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