Ответ
Паттерн Saga управляет распределенными транзакциями через последовательность локальных транзакций. Существует два основных способа координации:
1. Хореография (Choreography)
Децентрализованный подход, где сервисы общаются друг с другом через асинхронные события (например, через Kafka или RabbitMQ). Каждый сервис подписывается на события, которые ему интересны, и публикует свои собственные события после выполнения своей части работы.
- Принцип работы: Сервис A выполняет транзакцию и публикует событие
OrderCreated
. Сервис B слушает это событие, выполняет свою транзакцию и публикуетPaymentProcessed
. В случае ошибки сервис публикует событие сбоя, на которое реагируют другие сервисы для запуска компенсирующих транзакций. - Плюсы: Слабая связанность (loose coupling), высокая масштабируемость.
- Минусы: Сложность отслеживания и отладки общего процесса, так как нет центральной точки управления.
// Пример обработчика событий в сервисе платежей
func handleOrderCreated(event OrderCreatedEvent) {
if err := processPayment(event.OrderID); err != nil {
// Публикуем событие сбоя для запуска компенсации
publishEvent(PaymentFailedEvent{OrderID: event.OrderID, Reason: err.Error()})
} else {
publishEvent(PaymentSucceededEvent{OrderID: event.OrderID})
}
}
2. Оркестрация (Orchestration)
Централизованный подход, где один сервис — оркестратор — управляет всем процессом. Он вызывает нужные сервисы в правильной последовательности и запускает компенсирующие транзакции в случае сбоя.
- Принцип работы: Оркестратор вызывает сервис A, ждет ответа, затем вызывает сервис B. Если сервис B возвращает ошибку, оркестратор вызывает компенсирующую транзакцию для сервиса A.
- Плюсы: Централизованная логика, проще отслеживать состояние, легче отлаживать и управлять ошибками.
- Минусы: Оркестратор становится единой точкой отказа (SPOF) и может привести к сильной связанности.
// Упрощенный пример оркестратора
type SagaOrchestrator struct {
steps []SagaStep // Шаги: {Execute: func(), Compensate: func()}
}
func (s *SagaOrchestrator) Execute() error {
completedSteps := []SagaStep{}
for _, step := range s.steps {
if err := step.Execute(); err != nil {
s.compensate(completedSteps) // Откатываем выполненные шаги
return err
}
completedSteps = append(completedSteps, step)
}
return nil
}
func (s *SagaOrchestrator) compensate(stepsToCompensate []SagaStep) {
for i := len(stepsToCompensate) - 1; i >= 0; i-- {
stepsToCompensate[i].Compensate() // Вызов компенсирующих транзакций
}
}
Ключевые аспекты реализации:
- Идемпотентность: Все операции и компенсации должны быть идемпотентными, чтобы повторный вызов не менял результат.
- Хранение состояния: Состояние саги (какие шаги выполнены) нужно персистентно хранить (в БД, Redis), чтобы восстановиться после сбоя.
- Тайм-ауты: Для контроля времени выполнения шагов и автоматического запуска компенсации.