Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Що таке transactional outbox і навіщо він потрібен?

Проблема подвійного запису. Замовлення треба зберегти в базу і повідомити інші системи (черга, брокер повідомлень, вебхук). Це дві різні системи, і атомарно оновити обидві неможливо:

DB::transaction(function () use ($order) {
    $order->save();
});
$broker->publish(new OrderPlaced($order));   // впало тут - подію втрачено назавжди

Якщо поміняти місцями - можна опублікувати подію про замовлення, транзакція якого потім відкотиться.

Transactional outbox: подія записується в ту саму базу й ту саму транзакцію, що й зміна даних, - у спеціальну таблицю:

CREATE TABLE outbox (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    aggregate_type text NOT NULL,
    aggregate_id bigint NOT NULL,
    event_type text NOT NULL,
    payload jsonb NOT NULL,
    created_at timestamptz NOT NULL DEFAULT now(),
    published_at timestamptz
);
DB::transaction(function () use ($order) {
    $order->save();
    Outbox::create(['event_type' => 'OrderPlaced', 'payload' => [...]]);
});

Тепер або є і замовлення, і подія, або немає нічого.

Окремий процес-ретранслятор читає неопубліковані записи й надсилає їх у брокер:

SELECT * FROM outbox WHERE published_at IS NULL ORDER BY id LIMIT 100 FOR UPDATE SKIP LOCKED;

Після успішної відправки - позначає published_at (або видаляє запис). Альтернатива опитуванню - читати зміни таблиці з WAL через Change Data Capture (Debezium).

Що з цього випливає:

  • Доставка «щонайменше раз»: ретранслятор міг надіслати подію й упасти, не позначивши її. Отримувачі мають бути ідемпотентними (ID події + перевірка, чи вже оброблено).
  • Порядок подій зберігається в межах одного агрегата, якщо ретранслятор обробляє їх за id.
  • Таблицю outbox треба чистити - вона росте з кожною подією.

У Laravel частково цю задачу вирішують afterCommit для завдань і подій (відправити в чергу лише після успішного коміту), але це не захищає від падіння процесу між комітом і відправкою - для справжньої гарантії потрібен саме outbox.

Докладніше в документації: Pattern: Transactional outbox

Схожі питання