Architecture
Transactional Outbox Pattern
How placing an order becomes a confirmation email — across one Postgres transaction, a Debezium CDC hop, a Kafka topic, and two independently-scaled services.
Same transaction
CDC — tails WAL, no app code
Kafka produce / consume
Direct inspection
Metrics (Prometheus)
Traces (Tempo)
One transaction
rest-service writes the orders row and the outbox row in a single @Transactional call — never two systems to keep in sync.
CDC, not dual writes
Debezium tails Postgres's write-ahead log and republishes the outbox row — the only way an order ever reaches Kafka.
Blocks, never drops
The rate limiter (5 req/10s) blocks the consumer thread instead of rejecting. Lag grows under load; nothing is lost.
Trace survives the hop
The outbox row's trace_context column becomes a Kafka traceparent header, so one trace spans both services.