05
Deep dive — the Outbox pattern
The core problem with saga: when service A commits its local DB and needs to notify service B, if the notification fails, saga breaks. You cannot atomically "commit DB + send message" across systems (that's 2PC, which we're avoiding).
Outbox pattern: service A writes its DB change AND an "outbox" row in the same local transaction. A separate process polls outbox rows and publishes them to Kafka / a message bus. After successful publish, outbox row is marked sent (or deleted).
- DB commit happens: both business data + outbox row are written atomically (one-DB transaction — free).
- Poller sees new outbox rows, publishes to Kafka.
- If poller crashes after publishing but before marking sent → republishes on restart → at-least-once delivery. Consumers must be idempotent.
- If poller crashes before publishing → retries on restart → still at-least-once.
Net effect: atomicity between "change the DB" and "emit the event," without a distributed transaction. Every serious microservices system uses this (Debezium makes it turnkey via DB change-data-capture). Pair with idempotent consumers and sagas — this is the modern distributed transaction stack.