Error Envelopes as a Design Discipline
An error envelope is a stable, versioned interface whose sole job is to communicate failure semantics across boundaries. Adopting one is a promise: clients never need raw text, stack traces, or ad-hoc fields to react to errors. The promise holds only if you honor these constraints:
- Structural invariance — Same top-level fields for every error, regardless of cause/endpoint. Optional fields never invalidate the core shape; no conditional schemas or overloaded meanings.
- Two-layer identification — Human-facing text may change; machine-facing code/identifier is immutable once published. Clients branch on codes, never on text; changing meaning requires a new code.
- Semantic codes — Codes name what is wrong, not how it was detected (e.g.,
INVALID_INPUT, notJSON_PARSE_ERRORunless parsing is the contract). - Stable categories — Coarse, enumerable categories (validation, authorization, conflict, unavailable…) distinct from codes, changing rarely, so clients can reason at a higher level.
- Correlation id — Every envelope carries a correlation identifier generated at ingress and propagated unchanged through logs/traces; surfaced explicitly.
- Responsibility clarity — Envelope makes clear whether the fault is client, system, or dependency to drive retry/escalation decisions without guessing.
- Scoped sub-errors — Additive, scoped details (e.g., per-field validation) that never redefine the top-level error; each uses constrained vocabularies, not free-form prose.
- Versioned contract — Evolution rules are explicit: adding fields is backward-compatible; renames/removals or meaning changes are not. Breaking changes require a new envelope version.
- Transport-agnostic — Same envelope meaning over HTTP, MQ, jobs, or logs; transport statuses are mappings layered on top, not embedded.
- Public surface — Documented, reviewed, and tested like any interface. Tests assert envelope conformance; any internal exception not expressible in the envelope is a design failure.
With these constraints, error envelopes become a stabilizing structure: internal code can evolve, failure handling stays predictable, and clients gain a reliable language for reasoning about what went wrong—without needing to inspect system internals.