Как согласовать бизнес-операцию, затрагивающую несколько сервисов, если общей транзакции между ними нет?
Используют распределённую сагу: бизнес-операцию разбивают на локальные транзакции сервисов, а для каждого выполненного шага определяют продолжение или компенсирующее действие. Сага не даёт мгновенной атомарности, поэтому система некоторое время может находиться в промежуточном состоянии, но в итоге либо завершает операцию, либо переводит её в согласованное компенсированное состояние.
В монолитной системе несколько изменений обычно можно выполнить в одной транзакции базы данных. При разделении системы на сервисы данные оказываются в разных базах, а общая транзакция становится технически сложной, плохо масштабируется и связывает сервисы сильнее, чем нужно.
Сага появилась как способ управлять длительными бизнес-операциями без удержания общей блокировки и без требования к единому менеджеру распределённых транзакций. Её исходная проблема — согласовать последовательность локальных изменений при частичных сбоях сети, сервисов или инфраструктуры.
Например, оформление заказа включает резервирование товара, списание денег и создание доставки. После успешного резервирования платёжный сервис может стать недоступен, а после списания денег сервис доставки может отклонить заказ.
Если просто повторять всю операцию целиком, можно повторно списать деньги или создать несколько резервов. Если не обрабатывать отказ, система оставит заказ, деньги и остатки товара в противоречивых состояниях.
Сначала определяют шаги саги и их локальные транзакции. Для каждого шага фиксируют успешное завершение, возможные причины отказа, допустимые повторы и компенсирующее действие: отмена резерва, возврат платежа или отмена заявки на доставку.
Координация бывает двух основных видов. При оркестрации отдельный координатор явно запускает шаги, отслеживает их состояние и инициирует компенсации. При хореографии сервисы реагируют на события друг друга; это уменьшает роль центрального компонента, но усложняет понимание общего процесса, контроль циклов и диагностику.
Компенсация не является техническим откатом. Она выполняется отдельной бизнес-операцией и может сама завершиться не сразу или оказаться невозможной. Поэтому состояние саги, результаты шагов и ошибки нужно хранить явно, включая состояния «выполняется», «завершена», «компенсируется» и «требует ручного вмешательства».
Каждый обработчик шага и компенсации должен быть идемпотентным или защищённым уникальным бизнес-идентификатором. Это необходимо из-за повторной доставки сообщений, тайм-аутов и ситуации, когда действие выполнено, но подтверждение не дошло до координатора.
Для надёжности обычно разделяют фиксацию изменения в локальной базе и отправку сообщения через надёжный механизм, например outbox. Однако outbox решает доставку события из конкретного сервиса, а не согласование всей бизнес-операции.
Главный компромисс саги — отказ от мгновенной глобальной атомарности в пользу доступности и независимости сервисов. Если бизнес не допускает даже кратковременного промежуточного состояния, следует пересмотреть границы сервисов или отдельно оценить применение распределённой транзакции; сага не заменяет её во всех сценариях.
В интернет-магазине нужно зарезервировать товар, принять оплату и передать заказ в доставку. Рассматривались три варианта: общая распределённая транзакция, хореография событий и оркестрация саги.
Общая транзакция давала более строгую атомарность, но связывала независимые сервисы, требовала их совместного участия и плохо соответствовала длительным операциям. Хореография снижала центральную связанность, но усложняла контроль последовательности и расследование зависших заказов.
Выбрали оркестрацию саги. Координатор последовательно запускал резервирование, оплату и доставку, хранил состояние каждого шага, а при отказе доставки инициировал возврат платежа и снятие резерва. Повторные команды обрабатывались по идентификатору операции, а заказы, для которых компенсация не завершилась автоматически, попадали в очередь ручной обработки.
Такой вариант не сделал процесс мгновенно атомарным, но обеспечил наблюдаемое состояние операции, предсказуемую обработку частичных отказов и отсутствие повторных бизнес-действий при повторах сообщений.
Потому что между исходным действием и компенсацией могли произойти новые бизнес-события. Например, товар уже мог быть отгружен, а возврат платежа может требовать отдельной проверки. Компенсация должна быть самостоятельной бизнес-операцией с собственными правилами, статусами и обработкой ошибок, а не механическим вызовом «отменить всё».
Нельзя автоматически считать действие неуспешным и безусловно повторять его. Координатор должен использовать уникальный идентификатор операции, поддерживать запрос статуса или безопасный повтор и различать подтверждённый успех, подтверждённый отказ и неизвестный результат. Без этого возможны двойное списание, повторное резервирование или ошибочная компенсация.
Нужно заранее определить терминальные состояния процесса: успешное завершение, полная компенсация и состояние, требующее ручного вмешательства. Тайм-аут сам по себе не доказывает отказ: он лишь означает отсутствие ответа в заданный момент. Поэтому окончательное состояние должно устанавливаться по подтверждённому результату шага, согласованной политике повторов или процедуре разбора неопределённого состояния.