Contract Event
also called Designed Event vs Row Change, Deliberate Emission
An event a system deliberately emits with a schema it owns and a meaning it controls, as opposed to a captured row change that exposes the producer's table structure as a public interface.
Both the transactional outbox and change data capture close the gap between a state change and its publication. They differ in what they publish, and that difference determines the coupling.
An outbox publishes events you chose to emit — "payment authorised", "card issued" — with a schema you own. Change data capture publishes every row change, which means consumers become coupled to your table structure.
Why it matters
CDC's real cost is organisational rather than technical: a column rename breaks consumers who never told you they depended on it. The schema becomes a public interface by accident, and the producer loses the ability to refactor its own storage.
The outbox costs an explicit design step per event and buys the ability to change the implementation without breaking anyone.
Implementation patterns
- Choose the outbox when the event's semantics matter and consumers are external or long-lived — which describes most financial and business events.
- Choose CDC when you need every change and cannot enumerate events in advance — warehouse replication, search indexing, audit streams — and when the consumers are internal enough that schema coupling is manageable.
- Running both is common and correct: an outbox for the domain events that are a contract, CDC for the bulk replication that needs everything.
- Commit the outbox row in the same local transaction as the state change, which is the property that makes publication atomic with the change.
- Version the event schema and evolve it additively, since it is now a contract.
- Document the semantics, not just the shape: exactly when it fires, what each value means, and what it does not mean.
- Require idempotent consumers regardless. Both mechanisms are at-least-once — the relay may publish and crash before marking the row sent — and a non-idempotent consumer turns a correct delivery guarantee into a correctness bug.
Industry example
Financial platforms such as Brex, Ramp and Marqeta emit events with direct financial consequences downstream — an accounting entry, a customer notification, a reconciliation record. A duplicated or misinterpreted event is a money problem, which is why the deliberate-contract form dominates in that domain and why the event schema is treated with the same care as a public API.
The same platforms typically run CDC alongside it for analytical replication, where the consumers are internal and the coupling is acceptable.
Failure scenarios
- CDC used as a public event stream, freezing the producer's schema.
- Outbox rows published without idempotency at the consumer, producing duplicate financial effects.
- The outbox written outside the state change's transaction, which recreates the gap it was meant to close.
- Event semantics undocumented, producing semantic coupling that nothing detects.
- No reconciliation, so what the pipeline silently missed is discovered by a customer.
Trade-offs
The outbox requires enumerating events in advance, which is impossible for consumers whose needs are not yet known, and it adds a design step per event that CDC does not.
It also produces a second write on the hot path — cheap, since it is local and in the same transaction, but not free at very high volume.
The judgement is about the consumers: external or long-lived consumers justify the contract; internal analytical consumers usually do not.
Interview question
"You need your order service's changes to reach a search index, a warehouse and a partner's webhook. Which mechanism for each, and why not the same one for all three?"