Перейти к содержанию
Innovialabs

Обработка заказов через саги: согласованность оплаты, склада, доставки

Продвинутое3 мин

Коротко

Сага разбивает заказ на последовательность локальных шагов с компенсирующими действиями, а outbox и машина состояний гарантируют, что ни одно событие не потеряется и заказ не застрянет в противоречивом состоянии.

Как саги решают проблему согласованности

Когда оплата, склад и доставка — разные сервисы или внешние системы, одной транзакции на всё нет. Сага — это цепочка локальных транзакций, где у каждого шага есть компенсирующее действие: если шаг 3 провалился, шаги 2 и 1 отменяются в обратном порядке.

Типичная сага заказа:

ШагДействиеКомпенсация
1Создать заказ в статусе pendingОтменить заказ
2Зарезервировать товарСнять резерв
3Провести оплатуВернуть деньги
4Создать отправкуОтменить отправку
5Подтвердить заказ—

Итог — не мгновенная, а итоговая согласованность (eventual consistency): система может быть короткое время в промежуточном состоянии, но всегда приходит к корректному финалу.

Почему распределённый заказ ломается

  • Частичный успех. Деньги списаны, а резерв не создан из-за таймаута склада.
  • Двойная запись. Сервис записал заказ в базу, но упал до отправки события — остальные о заказе не узнали.
  • Повторы. Сеть повторила запрос, и оплата прошла дважды.
  • Неизвестный исход. Таймаут не означает отказ: платёж мог пройти.
  • Порядок событий. Отмена пришла раньше подтверждения.

Двухфазный коммит (2PC) между внешними API, платёжными провайдерами и службами доставки обычно недоступен, поэтому нужны саги.

Оркестрация или хореография

Оркестрация — центральный координатор (orchestrator) хранит состояние саги и по очереди вызывает сервисы.

Хореография — сервисы реагируют на события друг друга: OrderCreated → склад резервирует → StockReserved → оплата списывает и так далее.

КритерийОркестрацияХореография
Где логика процессаВ одном местеРаспределена по сервисам
НаблюдаемостьПроще: видно состояние сагиНужна трассировка событий
СвязанностьСервисы зависят от оркестратораСервисы зависят от событий
ПодходитДлинные процессы с ветвлениямиКороткие цепочки из 2-3 шагов

Для заказа с оплатой, складом и доставкой оркестрация обычно понятнее в поддержке.

Машина состояний заказа

Заказ должен иметь явные статусы и разрешённые переходы. Запрещённый переход — ошибка, а не тихое обновление.

pending -> stock_reserved -> paid -> shipping_created -> confirmed
pending | stock_reserved | paid -> compensating -> cancelled

Это защищает от гонок: если пришло событие PaymentSucceeded для уже отменённого заказа, система не «воскресит» его, а запустит возврат.

Transactional outbox

Чтобы не терять события при сбое, изменение состояния и событие пишутся в одной локальной транзакции: в таблицу заказов и в таблицу outbox. Отдельный процесс читает outbox и публикует сообщения в брокер.

BEGIN;
UPDATE orders SET status = 'stock_reserved' WHERE id = $1;
INSERT INTO outbox (aggregate_id, type, payload)
VALUES ($1, 'StockReserved', $2);
COMMIT;

Outbox даёт доставку at-least-once, поэтому каждый потребитель должен быть идемпотентным: хранить обработанные id сообщений или использовать idempotency key при вызове платёжного API.

Сценарии сбоев

  • Оплата отклонена. Компенсация: снять резерв, заказ в cancelled, уведомить клиента.
  • Таймаут оплаты. Не отменять сразу — запросить статус у провайдера или дождаться вебхука, затем решить.
  • Служба доставки недоступна. Повторять с backoff; после лимита — ручная обработка, а не автоматический возврат денег.
  • Компенсация сама упала. Повторять до успеха и выводить в алерты; компенсации должны быть идемпотентными.
  • Сообщение пришло дважды. Идемпотентный обработчик игнорирует повтор.

Частые ошибки

  • Вызывать внешние API внутри транзакции базы данных.
  • Считать таймаут отказом.
  • Не предусмотреть ручное вмешательство для «застрявших» саг.
  • Не логировать шаги саги с общим correlation id.

Частые вопросы

Если монолит вызывает внешние платёжные и логистические API, проблема та же: локальная транзакция не охватывает внешний мир. Машина состояний и outbox полезны и в монолите.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.