Ответ
Saga — это паттерн управления транзакциями в распределенных системах (например, в микросервисной архитектуре), который помогает поддерживать целостность данных без использования традиционных ACID-транзакций и блокировок.
Вместо одной большой транзакции, Saga разбивает бизнес-процесс на последовательность локальных транзакций. Каждая из этих транзакций атомарна и выполняется в рамках одного сервиса.
Ключевой принцип:
Если какой-либо шаг в последовательности завершается неудачно, Saga запускает компенсирующие транзакции в обратном порядке для отмены уже выполненных шагов. Компенсирующая транзакция — это операция, которая логически отменяет действие предыдущей транзакции (например, возврат средств вместо отмены платежа).
Способы реализации:
Хореография (Choreography):
- Сервисы общаются друг с другом через асинхронные события (events).
- Каждый сервис после выполнения своей части работы публикует событие. Другие сервисы подписываются на эти события и запускают свои локальные транзакции.
- Плюсы: Слабая связанность сервисов.
- Минусы: Сложно отслеживать общий статус процесса, так как нет центральной точки управления.
Оркестрация (Orchestration):
- Существует центральный сервис-координатор (оркестратор), который управляет всем процессом.
- Оркестратор последовательно вызывает нужные сервисы для выполнения локальных транзакций. В случае сбоя он же отвечает за вызов компенсирующих транзакций.
- Плюсы: Централизованная логика, проще понимать и отлаживать процесс.
- Минусы: Оркестратор может стать единой точкой отказа (Single Point of Failure).
Пример концепции на Go (Оркестрация):
// Каждый шаг — это вызов отдельного микросервиса
func placeOrderSaga(orderID string, userID string, money float64) error {
// Шаг 1: Зарезервировать товары
if err := inventoryService.ReserveItems(orderID); err != nil {
return fmt.Errorf("ошибка резервации: %w", err)
}
// Шаг 2: Списать средства с клиента
if err := paymentService.Charge(userID, money); err != nil {
// Компенсация для шага 1
inventoryService.CancelReservation(orderID)
return fmt.Errorf("ошибка оплаты: %w", err)
}
// Шаг 3: Создать доставку
if err := shippingService.CreateShipment(orderID); err != nil {
// Компенсация для шага 2 и 1
paymentService.Refund(userID, money)
inventoryService.CancelReservation(orderID)
return fmt.Errorf("ошибка доставки: %w", err)
}
return nil
}