В монолите операция «провести платёж» выполнялась в одной SQL-транзакции: списать со счёта, создать запись в транзакциях, отправить в провайдер, обновить статус. В микросервисной архитектуре эти шаги выполняются разными сервисами.
Мы реализовали оркестрационную сагу через отдельный сервис-координатор. Вот упрощённая логика оркестратора на Go:
type PaymentSaga struct {
sagaID string
steps []SagaStep
completed []SagaStep
}
type SagaStep struct {
Name string
Execute func(ctx context.Context, data *PaymentData) error
Compensate func(ctx context.Context, data *PaymentData) error
}
func (s *PaymentSaga) Run(ctx context.Context, data *PaymentData) error {
for _, step := range s.steps {
if err := step.Execute(ctx, data); err != nil {
log.Error("saga step failed",
"saga_id", s.sagaID,
"step", step.Name,
"error", err,
)
// Запускаем компенсацию в обратном порядке
return s.compensate(ctx, data)
}
s.completed = append(s.completed, step)
}
return nil
}
func (s *PaymentSaga) compensate(ctx context.Context, data *PaymentData) error {
for i := len(s.completed) - 1; i >= 0; i-- {
step := s.completed[i]
if err := step.Compensate(ctx, data); err != nil {
// Компенсация не удалась — помещаем в dead letter queue
log.Error("compensation failed, sending to DLQ",
"saga_id", s.sagaID,
"step", step.Name,
)
return s.sendToDLQ(ctx, step, data)
}
}
return nil
}
// Определение шагов саги для платежа
func NewPaymentSaga(sagaID string) *PaymentSaga {
return &PaymentSaga{
sagaID: sagaID,
steps: []SagaStep{
{
Name: "reserve_balance",
Execute: reserveBalance,
Compensate: releaseBalance,
},
{
Name: "process_payment",
Execute: sendToProvider,
Compensate: cancelPayment,
},
{
Name: "update_transaction",
Execute: markAsCompleted,
Compensate: markAsFailed,
},
{
Name: "send_notification",
Execute: notifyMerchant,
Compensate: func(_ context.Context, _ *PaymentData) error {
return nil // Уведомление не требует компенсации
},
},
},
}
}
Состояние каждой саги мы сохраняли в отдельную таблицу, что позволяло восстанавливать незавершённые саги после перезапуска оркестратора. За 6 месяцев работы в продакшене было обработано 37 миллионов саг, из них 0.003% потребовали компенсации, и ни одна транзакция не была потеряна.
Оставить комментарий