Warum Context in Go praktisch ist
Ich glaube jeder der mal mit Go gearbeitet hat, kam irgendwann an den Punkt wo er sich dachte: Wofür context.Context
und wieso brauche ich das? Und jetzt soll ich das auch noch überall durchschleifen. Was anfänglich mühselig und eher “verbose” sich anfühlt, stellt sich über länger eher als nützlich aus.
Was ist context.Context in Go?
Im Package context
geht es vornehmlich um den Typen Context
welcher dazu verwendet wird deadlines, Abbruchsignale oder Request gebundene Variablen durchzureichen. D.h. für Dich: Du kannst Anfragen die im Rahmen eines Requests stattfinden entsprechend abbrechen, Timeouts setzen und ähnliches. Auch kannst du Metainformationen innerhalb deiner Anwendung durchreichen - User ID, Request ID, etc.
Simples Beispiel:
ctx, _ := context.WithTimeout(context.Background(), time.Second * 10)
req, _ := http.NewRequest("GET", url, nil)
req = req.WithContext(ctx)
Hier würde der Request abgeborchen werden, wenn er nicht innerhalb von 10 Sekunden beendet ist.
Typische Fehler vermeiden
Gerade am Anfang macht man einige “typische” Fehler, wir verteufeln hier jetzt nichts und niemanden. Diese Fehler machen wir alle mal, wichtiger ist, dass wir sie nicht wiederholen.
- Context komplett ignorieren, ihn nicht durchreichen. Es kann nervig sein, aber reicht ihn überall durch, wo es Sinn macht.
context.Background
- resultiert meist aus Fehler 1. Es gibt Momente wo es Sinn macht, wenn man den Background Context nutzt - also einen Context der an die Applikations Laufzeit gebunden ist und somit beendet wird, wenn z. Bsp. euer Web-Server beendet wird. Datenbankverbindungen sind hier ein guter Kandiat. Die sind in der Regel nicht an einen Reuqest gebunden und sollten zwecks Connectionpooling auch die ganze Lebenszeit der Anwendung laufen.- Vollstopfen. Da geht noch mehr!
context.Context
ist generisch gehalten, in der Theorie könnt ihr alles durchschleifen was euch lieb ist, versucht das jedoch zu vermeiden. Am Ende müllt man sich den Context zu und es so ein geschmäckle von globalen Variablen. Beschränkt Euch Metadaten. - Kein Cancel. Schielt nach oben zum “simplen Beispiel”.
context.WithTimeout
gibt als zweites Argumentcancel
zurück, ich hab es mal unterschlagen. Ruft es immer auf, so das Resourcen freigegeben werden können, sollte es mal schneller gehen als gedacht. Besser wäre also soctx, cancel := context.WithTimeout(context.Background(), time.Second * 10) defer cancel() req, _ := http.NewRequest("GET", url, nil) req = req.WithContext(ctx)
Best practises
- ✅ Context immer an untergeordnete Funktionen weitergeben
- ✅ Nutze context.WithTimeout() oder context.WithDeadline() bei externen Calls
- ✅ Ruf cancel() immer auf
- ✅ Halte dich an die Signatur: func(ctx context.Context, …) - Ich mag es ordentlich und gleich. Daher hat
context.Context
bei mir immer Fastlane in den Signaturen.
Fazit
Ich machs kurz: context.Context
gut. Ignorieren böse.