The Envelope: A Box That Describes What’s Inside
A payload wrapped with a fixed header works for one purpose. It breaks the moment a second one shows up.
A real envelope carries a single letter and a single address. Most technical envelopes copy that shape exactly. A typed payload, plus some meta information sitting alongside it as a header.
This is genuinely useful. A consumer can read the header and decide whether to bother deserialising the payload at all. Nobody has to release a new version just because they don’t care about this particular type. Two decades of systems have been built successfully on exactly this pattern.
It only has one purpose, though. That’s where it starts to strain.
If you are not yet a member of medium.
What Breaks: The X-Header Problem
Add a second concern and there’s only one place open to put it. Ownership. Security. Observability. Each one wants somewhere to live, and the meta information section is the only door left unlocked.
This starts out organised. It rarely stays that way. Different teams add their own conventions, at their own pace, and the header slowly turns into a junk drawer nobody fully understands. HTTP’s X-Headers went through exactly this. What began as a handful of sensible extensions became a sprawl of overlapping, half-documented conventions.
There’s an alternative. Fold the new concern directly into the payload instead. That avoids the junk-drawer problem, but creates a different one. Every producer and every consumer now has to move together, in lockstep, every time. A distributed system quietly turns into a monolith wearing microservice clothing.
Neither option is good. Both are symptoms of the same root cause. The envelope was only ever built for one purpose.
The Core Move: The Box, Not the Letter
Think about the cardboard box that lands on your doorstep instead. Its shape stays fixed no matter what’s inside. A book, a jacket, three separate parcels bundled together. The box doesn’t need to know or care.
Apply that shape to the envelope. The header stops being a flat bag of properties and becomes a hub. It references a set of independently typed, self-describing aspects rather than holding convention-based clutter directly. Each aspect is its own well-defined thing, addressable and versioned on its own terms. The header itself is no exception. It’s a strongly typed aspect too, not a special top-level structure sitting outside the pattern it describes.
Adding a new aspect doesn’t touch the others. Security can evolve. Tracing can evolve. A brand new concern nobody has thought of yet can be added later, as its own aspect, without a single existing consumer needing to change. Most of the time, it isn’t even the application developer adding it. The communication stack itself can attach a tracing aspect, or a hybrid logical clock, on the way out, without the code making the request ever needing to know it happened.
A concrete case makes this less abstract. Two services communicating over mutual TLS already carry the caller’s authority as part of the connection itself. Rather than treating that as something outside the contract, an identity aspect can describe it directly: this is who’s asking, this is what they’re authorised to do. The request, the identity, and the authority stop being disparate concerns bolted together by convention. They become three aspects the contract states plainly.
Composability All the Way Down
Content isn’t limited to one fixed type, either. An envelope’s content can itself be another envelope. Classic or evolved, doesn’t matter.
Get Stephen James Price’s stories in your inbox
Join Medium for free to get updates from this writer.
That opens up nesting. A box inside a box inside a box, and at every layer, the contents can still describe themselves. Nobody needs a master schema for the whole structure up front. Each layer only needs to understand its own aspects.
Trusting an Aspect Without Opening the Whole Box
A declared primary purpose lets something downstream know roughly what to expect inside an envelope, without needing to deserialise everything just to check. Closer to a customs label than a search.
Take a concrete case. A business application constructs an envelope carrying its payload. In post-processing, a framework computes an integrity aspect over that payload and attaches it. A consumer, later, can inspect just the integrity aspect and decide whether the envelope is safe to process further, before ever touching the payload itself.
This is the same trust mechanism this series already established for the Composer’s plan. A signature applied at the point something was composed, checked before anything built on top of it is trusted. Here it’s applied to the whole envelope instead of a single plan. Readable and actionable are not the same as trustworthy. Something has to close that gap, and it isn’t the contents themselves.
The Rosetta Stone
OpenAPI, AsyncAPI, gRPC. Different standards, built by different communities, at different times, for what turns out to be largely the same underlying job.
Pull any of them apart and three concerns show up, every time. An operation contract: inputs, outcomes, identity, entitlements. A communication contract: request and response, event, RPC, however the call actually gets made. Deployment details: how it’s actually reached right now, early bound or late bound.
Aspects give these three concerns a shared vocabulary instead of inventing a sixteenth competing standard. Not a new unifying protocol. A Rosetta Stone, letting existing ones map to each other through a common structure underneath.
An aside worth sitting with: a relational database already does this, and has for decades. The communication interface was never coupled to the internal tables or stored procedures. The contract lived inside the system. The interface just gave you a way in. Most distributed systems, somewhere along the way, forgot that separation was ever available to them.
Lenses, Properly
An earlier piece in this series introduced lenses briefly, as bidirectional, composable transformations, borrowed from the functional programming lens. Worth finishing that thought here.
Two things the FP version doesn’t fully capture. First, lossiness has to be a declared, honest property, not an assumed round-trip. A hospital record and a driving licence record might both carry a blood type, but projecting one into the other loses the Rhesus factor along the way. The lens should say so plainly rather than pretend the projection is lossless.
Second, a lens isn’t only for moving between versions of one specification. It also connects entirely different specifications to each other. One classification scheme into another. A CSV into Parquet. The lens is what lets an aspect written for one consuming domain be projected honestly into the shape a different domain actually needs.
Close
Because everything here is self-describing and composable, the transport structure itself becomes a reversible decision. Not something committed to on day one and lived with forever after.
One last thought, worth sitting with rather than developing further. A message in flight and a record at rest share more structure than the words suggest. It is just data, after all.






