The Code Codex
Theme
← Notes / Architecture

note · text21 Oct 2026 · 1 min

Transactional Outbox

The problem: a service needs to save an order in the database and publish the OrderCreated event to Kafka. They are two systems, with no shared transaction:

  • It saves and crashes before publishing: the order exists, but nobody finds out.
  • It publishes and the database commit fails: other services react to an order that doesn’t exist.

The solution: write the event to an outbox table, in the same database and in the same transaction as the order. A separate process reads that table and publishes to the broker.

Database

reads

publishes

1 transaction

Orders service

orders

outbox

Relay

polling or CDC

Kafka

Database

reads

publishes

1 transaction

Orders service

orders

outbox

Relay

polling or CDC

Kafka

BEGIN;
INSERT INTO orders (id, status) VALUES (42, 'CREATED');
INSERT INTO outbox (aggregate_id, type, payload) VALUES (42, 'OrderCreated', '{"id":42}');
COMMIT;

Either both rows exist, or neither does. The relay that publishes can be:

  • Polling: a job that periodically reads unpublished records and marks the ones sent.
  • CDC: Debezium reads the database transaction log and publishes every insert into the outbox, without extra queries.

Since the relay can crash between publishing and marking the record, the event can go out twice: the guarantee is “at least once”, and consumers need to be idempotent.

See also: Kafka and CQRS.