Eventual consistency and the outbox

Microservices · lesson 11 of 33 · 4 min read

Accept that copies lag for a moment, and publish events atomically with the outbox pattern.

Open this lesson in the learning hub

Key points

  • Once data lives in several services, "consistent" means "consistent in a moment". Usually milliseconds, occasionally minutes.
  • The classic bug: commit to the database, then publish the event, then crash. The write is live and nobody was told.
  • Outbox pattern: write the business row and the event row in the same local transaction. A relay reads the outbox and publishes.
  • The relay is Debezium (change data capture) or a boring polling job. Both give at-least-once delivery, which is why consumers dedupe.
  • Design the UI for the lag. Show a processing state, read writes back from the service that owns them, and never promise instant truth.

Example

-- PostgreSQL. One transaction, two writes: the fact and the announcement.
BEGIN;

INSERT INTO orders (id, customer_id, status, total_cents)
VALUES ('o-1042', 'c-77', 'PLACED', 4999);

INSERT INTO outbox (id, aggregate_id, type, payload, created_at)
VALUES (gen_random_uuid(), 'o-1042', 'OrderPlaced',
        '{"orderId":"o-1042","sku":"JCH-9","qty":1}'::jsonb, now());

COMMIT;

-- The relay claims a batch, publishes it, then stamps published_at.
-- SKIP LOCKED lets several relay instances run without stepping on each other.
SELECT id, type, payload
FROM outbox
WHERE published_at IS NULL
ORDER BY created_at
LIMIT 100
FOR UPDATE SKIP LOCKED;

Never write to the database and the broker separately; write both rows in one transaction.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Microservices course, and every lesson in it is listed on the Microservices contents page.