Как обрабатываются откаты в паттерне саги
Что проверяют
Понимаете ли вы, что в саге нет общего отката: каждая локальная транзакция уже закоммичена, и отменить её можно только новой операцией. Следом спрашивают, что будет, если упадёт сама компенсация, и чем оркестрация отличается от хореографии.
Как отвечать
- компенсирующая транзакция: у каждого шага есть обратное действие с бизнес-смыслом. Списали деньги, компенсация делает возврат, а не удаляет запись о платеже;
- обратный порядок: если упал шаг C, компенсируются уже выполненные B и A, именно в таком порядке. Сам C откатывается своей локальной транзакцией;
- классы шагов: компенсируемые, поворотный (pivot, после его успеха сага идёт только вперёд) и повторяемые, которые после pivot доводятся ретраями до успеха;
- оркестрация: координатор хранит состояние саги и сам вызывает компенсации. В Java это Temporal (класс
SagaсaddCompensation()иcompensate()), Camunda, Axon Framework; - хореография: сервисы реагируют на события друг друга, например
PaymentFailed, и компенсируют себя сами. Меньше связности, но ход процесса труднее отследить; - надёжность: состояние и событие пишутся атомарно через transactional outbox, доставка at-least-once, поэтому компенсации обязаны быть идемпотентными.
Упомяните изоляцию. Промежуточные состояния саги видны другим, это грязное чтение на уровне бизнеса. Лечится семантической блокировкой: заказ в статусе PENDING нельзя отгрузить, пока сага не завершилась.
Где спотыкаются
Называют компенсацию откатом в базе. Отправленное письмо и вызов внешнего API не откатить, компенсация здесь это новое действие, например письмо об отмене.
Компенсируют только предыдущий шаг и забывают про остальные или запускают компенсации в прямом порядке.
Не продумывают сбой компенсации. Её нельзя пропустить: повторяют с экспоненциальной задержкой, после исчерпания попыток отправляют в dead letter и на ручной разбор. Без идемпотентности повторная доставка события вернёт деньги дважды.