Platforms
Event contracts between bounded contexts
Two teams agree on a payload, ship, and discover six months later that they never agreed on what it meant. The schema was never the contract.
A shared event looks like the cheapest possible integration. One team publishes, another subscribes, nobody blocks anybody. It stays cheap right up to the first time the meaning drifts — and meaning drifts silently, because the schema still validates.
The schema is the smaller half
OrderPlaced { orderId, customerId, total, currency } tells a consumer what fields arrive. It does not tell them:
- Whether
totalincludes tax, and in which jurisdiction that was decided - Whether the order can still be cancelled when this fires
- Whether the same
orderIdcan appear twice - What the producer will do if the consumer is down for an hour
Every one of those is a decision someone made. If it is not written next to the schema, each consumer will guess, and they will guess differently.
Write the three things a schema cannot say
When it is emitted. Not "on order placement" — the exact transition. After payment authorisation succeeds and before fulfilment is notified. A consumer that assumes it fires earlier will build a feature on a state that does not exist yet.
What it promises about delivery. At least once, at most once, or exactly once in the only sense that is real: idempotent handling on the consumer side. Say which, and say what the deduplication key is.
What is allowed to change. Adding an optional field is safe. Narrowing an enum is not, and it looks identical in a diff. Name the compatibility rule so a reviewer can apply it.
Test the boundary from both sides
The single most useful thing a producer can do is publish a contract test that a consumer runs in their own pipeline. Not a shared integration environment — those tell you the two systems worked once, on that day, with that data.
producer: publishes example payloads for every version it claims to support
consumer: asserts its handler accepts each one and produces the expected state
CI: both run on every change, in both repositories
When a producer breaks compatibility, it fails in the producer's pipeline, with the name of the consumer that will break. That is a conversation before a deploy instead of an incident after one.
Version the meaning, not only the payload
A field added is a new version of the payload. A rule changed — tax now excluded from total — is a new version of the meaning, and it needs a new event type, not a new field. Consumers cannot detect a semantic change by inspecting a message, which is exactly why it has to be visible in the name.
The teams that stay fast are not the ones with the fewest contracts. They are the ones whose contracts say enough that nobody has to read the producer's source to use them.
Read next
- A model inherits every obligation your data already had
Adding an AI feature does not create a new compliance regime. It moves personal data into a component that is harder to inspect, and the duties travel with it.
- Runbooks that survive a reorganisation
Operational documentation is usually written for the person who already knows. Write it for the third reader instead — the one who arrives at 03:00, eighteen months from now.