Once the reference architecture splits truth across systems — stock in the WMS, orders in the OMS, money in the ERP — every copy is a consistency liability. This page is the pattern set that keeps the copies honest, ordered from the mistake everyone makes to the safety nets everyone eventually adds.
The root defect: a service writes its database and publishes an event as two separate operations. Whichever order you choose, a crash between them either loses the event (downstream never hears) or emits a lie (downstream hears about a write that rolled back). At any meaningful volume this is not hypothetical — it is a scheduled appointment.
The fix is the transactional outbox: write the state change and the outgoing event into the same database inside one ACID transaction (the event goes to an outbox table); a separate relay reads the outbox and publishes to the broker, marking rows sent. The guarantee: an event exists if and only if the commit happened. The relay is at-least-once — which is fine, because…
Every consumer must make redelivery harmless (IntegrationPatternVocabulary, idempotent receiver; ExactlyOnceAndDeliveryGuarantees for why the transport can't save you). The two working techniques:
When a system won't tell you about changes (legacy ERP, a platform with weak webhooks), CDC reads the database's own replication log and turns row changes into events — no polling, no application modification, ordered and complete. Debezium (open source, Kafka Connect-based; also runs standalone via Debezium Server) is the default engine, with first-class Postgres and MySQL support.
Commerce uses where CDC shines: feeding the analytics warehouse without touching production query capacity; extracting events from systems you can't modify; auditing every change to money-adjacent tables. The caveat: CDC events are row deltas, not domain events — "orders.status: 'paid'→'allocated'" needs a translation layer before it means "OrderAllocated" to consumers. For systems you own, prefer the outbox (which emits real domain events); use CDC for the systems you don't.
Sellable stock on the storefront is a delayed projection of WMS truth; the delay is the oversell window, and it cannot be zero (even sub-second sync loses to two customers clicking simultaneously on the last unit). The mature posture is not eliminating the window but engineering it:
Every pattern above reduces drift; none eliminates it (bugs, manual edits, crashed relays, partner systems lying). Scheduled reconciliation is mandatory at every tier — it is the pattern that turns silent corruption into a morning alert:
Design rule: reconciliation compares systems of record against projections per the ownership table — if a reconciliation can't say which side is right, the ownership table has a hole in it.
The storefront's sellable-stock number, the ops dashboard, the search index — these are read models: purpose-built, eventually-consistent projections fed by events (CqrsPattern). Naming them as such clarifies obligations: a projection may lag (display its staleness honestly), must be rebuildable from the source of truth (the enterprise-tier argument for a replayable log — IntegrationBackboneChoices), and must never be written to directly.
| Tier | Consistency kit |
|---|---|
| Garage | One ACID database doing the work + nightly reconciliation query + oversell script |
| Growth | Outbox + idempotent consumers + measured sync loop + daily reconciliation with alerts |
| Enterprise | All of the above + CDC to warehouse + replayable log + continuous reconciliation dashboards |