Короткий ответ
Transactional Outbox решает проблему dual write, записывая событие в отдельную таблицу outbox в той же транзакции БД, что и бизнес-изменение, а отдельный процесс затем читает outbox и публикует события в очередь.
Как это работает подробнее
Бизнес-операция и вставка строки в outbox происходят в одной локальной транзакции БД атомарно; relay-процесс (poller или CDC, например Debezium) читает новые строки outbox и публикует их в брокер.
Пример кода
example.sql
BEGIN;
UPDATE orders SET status = 'paid' WHERE id = 42;
INSERT INTO outbox (id, event_type, payload, created_at)
VALUES (gen_random_uuid(), 'order.paid', '{"order_id": 42}', now());
COMMIT;Что использовать на практике
- Практика. Избавляет от ситуации, когда БД-транзакция закоммитилась, а публикация в очередь не прошла; relay обеспечивает at-least-once публикацию, и consumer всё равно должен быть идемпотентным.
Подводные камни
- Риск. Публиковать событие до коммита транзакции БД (если транзакция откатится — событие о несуществующем изменении уже ушло); публиковать после коммита отдельным не атомарным вызовом без outbox.