Что такое Saga-паттерн в распределённых системах?
Saga – паттерн управления распределёнными транзакциями, при котором длинная бизнес-транзакция разбивается на последовательность локальных транзакций. Каждая локальная транзакция обновляет данные в своём сервисе и публикует событие, запускающее следующий шаг; при ошибке выполняются компенсирующие транзакции для отмены уже выполненных шагов. Это как бронирование путёвки: если отель забронирован, но билеты на самолёт закончились – отменяем бронь отеля.
Почему нужна Saga
В микросервисной архитектуре каждый сервис имеет свою БД. Обычная ACID-транзакция через несколько БД невозможна (или крайне дорогая – двухфазный коммит, 2PC). Saga обеспечивает согласованность в конечном счёте (eventual consistency).
Пример: перевод между счетами в разных сервисах
Пример кода
Успешный сценарий:
Сервис Платежей Сервис Счетов Сервис Уведомлений
│ │ │
1. Создать платёж (PENDING) │ │
│────── DebitAccount ────▶│ │
│ 2. Списать со счёта │
│◀──── AccountDebited ────│ │
3. Подтвердить платёж │ │
│─── PaymentCompleted ───▶│───── Notify ──────────▶│
│ │ 4. Уведомить клиента
Ошибка на шаге 2 (недостаточно средств):
Сервис Платежей Сервис Счетов
│ │
1. Создать платёж (PENDING) │
│────── DebitAccount ────▶│
│ 2. ОШИБКА: недостаточно средств
│◀──── DebitFailed ───────│
3. Компенсация: отменить платёж (CANCELLED)
Два подхода к реализации
Оркестрация (Orchestration)
Центральный Saga Orchestrator управляет последовательностью шагов. Он знает весь процесс и явно вызывает каждый шаг:
Пример кода
@Service
public class TransferSagaOrchestrator {
public void execute(TransferCommand cmd) {
try {
// Шаг 1: списание
accountService.debit(cmd.getFromAccountId(), cmd.getAmount());
// Шаг 2: зачисление
accountService.credit(cmd.getToAccountId(), cmd.getAmount());
// Шаг 3: уведомление
notificationService.notify(cmd);
} catch (CreditFailedException e) {
// Компенсация шага 1
accountService.reverseDebit(cmd.getFromAccountId(), cmd.getAmount());
throw new TransferFailedException(e);
}
}
}
Хореография (Choreography)
Каждый сервис слушает события и сам решает, что делать. Нет центрального координатора:
Пример
PaymentCreated → Сервис Счетов слушает → списывает → AccountDebited
AccountDebited → Сервис Платежей слушает → подтверждает → PaymentCompleted
PaymentCompleted → Сервис Уведомлений слушает → уведомляет
Сравнение подходов
| Аспект | Оркестрация | Хореография |
|---|---|---|
| Управление | Центральный координатор | Децентрализованное |
| Сложность | Логика в одном месте | Размазана по сервисам |
| Связанность | Orchestrator знает все шаги | Сервисы знают только свои события |
| Отладка | Проще (один поток) | Сложнее (нужна распределённая трассировка) |
| Масштабирование | Orchestrator – потенциальное узкое место | Лучше масштабируется |
Когда что выбирать
- Оркестрация предпочтительна, когда процесс линейный, шагов немного (3-5), и важна наглядность потока. Проще для отладки и мониторинга.
- Хореография предпочтительна, когда много независимых реакций на одно событие, и сервисы должны быть максимально автономны.
Итог
Saga – обязательный паттерн для распределённых систем, где нужна согласованность данных между сервисами. Ключевое правило: каждый шаг саги должен иметь компенсирующую операцию. Если шаг нельзя компенсировать (например, отправка email), его ставят последним в цепочке.
На собеседовании: Интервьюер ожидает знание обоих подходов (оркестрация vs хореография) и понимание компенсирующих транзакций. Частая ошибка – не упоминать eventual consistency и предполагать, что Saga обеспечивает ACID-гарантии.