WithValue glisse une donnée dans le contexte — clé typée, jamais une string. Un request ID y a sa place ; une dépendance non.WithValue slips data into the context — typed key, never a string. A request ID belongs there; a dependency doesn't.
Les deux premiers numéros du volume 8 ont couvert le signal dans le contexte — l'annulation et le temps. context.WithValue ajoute une troisième dimension : la donnée. WithValue prend un contexte parent, une clé, et une valeur, et retourne un nouveau contexte qui les mémorise. La valeur est lisible en aval par ctx.Value(clé) — sans modifier les signatures des fonctions intermédiaires. C'est cette propriété qui rend WithValue tentant : plus besoin de passer un request ID à chaque couche, on le glisse dans le contexte au niveau du middleware et on le lit où on en a besoin. Mais cette même propriété est aussi son danger : elle rend les dépendances implicites, invisibles à la compilation, et difficiles à tracer. Pour choisir la bonne clé, la règle est nette : jamais une string nue, toujours un type défini dans le package qui l'utilise. La raison tient en un mot : collision. Si deux packages utilisent la chaîne "user" comme clé, ctx.Value("user") retourne le premier ou le dernier selon l'ordre d'écriture — sans avertissement, sans erreur. Un type privé non-exporté est, par définition, unique à son package : deux types contextKey définis dans deux packages différents sont deux types distincts en Go, même s'ils ont le même nom. C'est exactement ce qu'on veut.The first two volume 8 issues covered the signal in the context — cancellation and time. context.WithValue adds a third dimension: data. WithValue takes a parent context, a key, and a value, and returns a new context that stores them. The value is readable downstream via ctx.Value(key) — without modifying intermediate function signatures. It's this property that makes WithValue tempting: no more passing a request ID through every layer, you slip it into the context at middleware level and read it where needed. But that same property is also its danger: it makes dependencies implicit, invisible to the compiler, and hard to trace. For choosing the right key, the rule is sharp: never a bare string, always a type defined in the package that uses it. The reason fits in one word: collision. If two packages use the string "user" as a key, ctx.Value("user") returns the first or last depending on write order — without warning, without error. A private unexported type is, by definition, unique to its package: two contextKey types defined in two different packages are two distinct types in Go, even if they have the same name. That's exactly what you want.
// Un type de clé privé — jamais une string nue. type contextKey int const requestIDKey contextKey = iota // Écrire dans le contexte func withRequestID(ctx context.Context, id string) context.Context { return context.WithValue(ctx, requestIDKey, id) } // Lire depuis le contexte — avec une assertion de type sûre func requestIDFrom(ctx context.Context) (string, bool) { id, ok := ctx.Value(requestIDKey).(string) return id, ok } // Dans un middleware HTTP : func requestIDMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { id := r.Header.Get("X-Request-ID") if id == "" { id = uuid.New().String() } ctx := withRequestID(r.Context(), id) next.ServeHTTP(w, r.WithContext(ctx)) }) }
Le contexte porte des métadonnées de la requête, pas des dépendances du programme. La règle : « est-ce que cette valeur change d'une requête à l'autre ? » Si oui, le contexte est le bon endroit. Sinon, passe-la explicitement.Context carries request metadata, not program dependencies. The rule: "does this value change from one request to the next?" If yes, context is the right place. If not, pass it explicitly.
« Cette valeur est-elle une métadonnée de la requête — un ID, un token, une trace — ou une dépendance du programme — un logger, une base, un client ? » Si c'est une métadonnée : WithValue avec une clé typée, un getter, un setter. Si c'est une dépendance : passe-la explicitement dans les constructeurs ou les arguments. Ce test simple évite 90 % des abus de context.Value dans une base de code."Is this value a request metadata item — an ID, a token, a trace — or a program dependency — a logger, a database, a client?" If it's metadata: WithValue with a typed key, a getter, a setter. If it's a dependency: pass it explicitly in constructors or arguments. This simple test avoids 90% of context.Value abuses in a codebase.
Chaque appel à context.WithValue crée un nouveau nœud dans la chaîne de contextes. ctx.Value(key) remonte cette chaîne nœud par nœud jusqu'à trouver la clé. Dans une chaîne courte — deux ou trois valeurs — c'est négligeable. Mais si on utilise le contexte comme un sac général et qu'on y empile des dizaines de valeurs, la recherche devient linéaire dans la profondeur de la chaîne. C'est une raison de plus de n'y mettre que ce qui y a vraiment sa place — un nombre raisonnable de métadonnées de requête, pas toutes les dépendances du service.Each call to context.WithValue creates a new node in the context chain. ctx.Value(key) climbs this chain node by node until it finds the key. In a short chain — two or three values — it's negligible. But if you use context as a general bag and pile dozens of values into it, the lookup becomes linear in the chain's depth. It's one more reason to put only what truly belongs there — a reasonable number of request metadata items, not all service dependencies.