Should a shared failure-body factory take the correlation id as a parameter or read it from request-scoped context?
answer
- the factory should need nothing from the caller
- parameters mean discipline at every call site
- ambient value is bound to an execution unit
- never invent an id at write time
- define the empty-context path and log it
basics
~20 sRead it from request-scoped context. A parameter works but puts the burden back on every call site, which is the drift the factory exists to remove. The factory must also define what it does when that context is empty.
solid answer
~50 sBoth options produce the right body when everyone does the right thing, which is the wrong test - the factory exists precisely so no call site has to remember anything. Reading an ambient, request-scoped value at write time means a handler cannot omit the id, cannot pass the wrong one, and does not have to thread it through its own signatures to get it. The two consequences to be ready for are: the factory must never *generate* an id itself, because one invented at write time matches nothing already logged for that request; and the ambient value may be empty once work has moved off the request's original execution unit, so the factory needs a defined degradation - omit the field or fall back to a value it also logs - rather than throwing while building a failure response.
go deeper
Learn the shape first: the id is put into a per-request context at the edge of the service, and the code that builds the failure body reads it from there. Nothing in between has to pass it along.
Explain why an ambient read beats a parameter - it removes the per-call-site discipline the factory exists to remove - and state the two rules: never fabricate an id at write time, and define what happens when the context is empty.
Show the operational edge: context is bound to an execution unit, so a handoff to another thread or task can empty it. Diagnose by logging the id at entry and at write, and make the missing-context path visible in monitoring.
Decide the trust policy for ids arriving from callers and the cross-service convention: which header, who generates when absent, what validation applies, and whether a downstream service may replace an id or must only extend the trace.
Every failure body in the contract carries a correlation id, and the shared factory is what stamps it. The design question is how the factory *obtains* it: as an argument every caller supplies, or as an ambient value bound to the current request that the factory reads for itself. ## Judge the options by what they demand of call sites | Property | Id passed as a parameter | Id read from request-scoped context | |---|---|---| | Can a call site omit it? | Yes, by passing a placeholder or the wrong value | No - the call site is not involved | | Effect on intermediate code | The id must be threaded through signatures that have no other use for it | None | | Behaviour when work leaves the request's execution unit | Still correct, because the value travelled with the call | Empty unless the framework propagates the context | | Testability | Trivial - pass a fixed value | Needs the context populated, or an injectable reader | Threading the id as a parameter is not wrong, and in a small, strictly synchronous codebase it is honest and obvious. But it reintroduces the failure mode the shared factory was built to eliminate: a discipline that every author must observe at every call site. Intermediate functions that care nothing about correlation ids start carrying one because something beneath them might raise a failure. That is a smell, and it is how parameters get defaulted to empty strings 'just for now'. ## Ambient context, precisely 'Request-scoped context' means a value bound to the unit of work handling this request and readable without being passed - typically by the framework's own request context, or by a mechanism that associates state with the executing thread, task or coroutine. The property it provides is that code arbitrarily deep in a call chain can read a value that was set once at the edge, without the intervening frames knowing it exists. That property is what the factory needs, and it comes with one structural caveat: **the binding is to an execution unit, not to the universe.** Frameworks differ here - some propagate the context automatically across their own asynchronous boundaries, others require the value to be captured and restored explicitly when work is handed to another executor. Either way, the assumption 'a correlation id is always present' is exactly the kind of quantifier that turns into a production incident. ## The two rules the factory must follow 1. **Never generate an id at write time.** If the context is empty and the factory quietly creates a fresh identifier, the body looks perfect and is worthless: the value the client quotes in a support ticket appears in no log record, because every log line for that request was written with whatever the request actually carried, or with nothing. A fabricated id is worse than a missing one, because it survives inspection. 2. **Degrade on purpose, and loudly.** Define the behaviour for an empty context up front - omit the field entirely, or emit a clearly marked value - and log a warning at that point so the gap is visible in monitoring instead of only in a confused support conversation. What the factory must not do is throw: raising an exception while building a failure response replaces a usable error with an unusable one, and the second failure has no id either. ## The id as untrusted input If the id can be seeded from an inbound request header so that a caller's trace continues through your service, then it is client-controlled text that you are about to put in a response body and in log records. Constrain it before it is accepted into the context: a length cap and a restricted character set. Otherwise the body becomes a vehicle for whatever a caller chose to send, and the log line becomes a place where a caller can inject structure. Validating at the point the context is populated - not in the factory - keeps the rule in one place too. Concretely, before an inbound value is accepted: - **cap its length**, so a caller cannot inflate every log line and every failure body; - **restrict the character set** to something like alphanumerics and separators, so the value cannot carry structure into a log record or markup into a rendered payload; - **generate your own** when the inbound value is missing or rejected, rather than leaving the request without an id; - **record which branch fired**, so a spike of rejected inbound ids is visible rather than silent. ## How to keep it testable Ambient state is harder to test than a parameter, and the usual answer is a narrow seam: the factory depends on a small reader abstraction with one operation - give me the current correlation id, if any - which is backed by the framework's context in production and by a fixed value in tests. That keeps the call sites free of the parameter while leaving the factory's behaviour, including its empty-context path, directly testable. ## What a strong answer sounds like Ambient read, because the factory's purpose is to remove per-call-site discipline; never fabricate; define and log the missing-context path; validate the id if it can arrive from outside; and keep a seam so the two interesting paths - present and absent - are both covered by tests.
- Why is a correlation id generated inside the factory worse than no id at all?Because it looks correct and is untraceable. The value in the response was created after every log record for that request was written, so support searches the logs with an identifier that appears nowhere. A missing field prompts investigation; a fabricated one ends it with a false negative. If nothing is in context, omit the field or mark it clearly, and log that the context was empty.
- Your factory reads ambient context, but ids go missing on one endpoint only. What do you suspect?That the endpoint hands work to another thread, task or executor, and the context is not propagated across that boundary, so the read returns empty by the time the failure is rendered. Confirm by logging the presence of the id at entry and at the write point. The fix is to capture and restore the context around the handoff, or to use the framework's propagation support for it.
- Should the service accept a correlation id supplied by the caller in a request header?It is useful for end-to-end tracing across services, but it is untrusted input heading for a response body and your log records. Accept it only after a length cap and a restricted character set, applied where the context is populated rather than in the factory, and generate your own when the inbound value is absent or rejected.
saying these in an interview costs you the question
- Insists threading the id through every signature is the only correct design
- Has the factory generate a fresh id when context is empty
- Assumes ambient context is present no matter where the code runs
- Lets the factory throw when the correlation id is missing
- Echoes a caller-supplied id into body and logs without any validation
- Treats an untestable ambient read as acceptable because it is convenient