Gymterview
senior

Что такое 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-гарантии.