← Назад к вопросам

Для чего нужен паттерн Saga?

3.0 Senior🔥 111 комментариев
#REST API и микросервисы#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Паттерн Saga: управление распределённо транзакциями

Saga — это архитектурный паттерн для управления долгоживущими распределёнными транзакциями в микросервисной архитектуре. Он решает проблему, что в системе из независимых сервисов невозможно использовать классические ACID транзакции БД.

Проблема, которую решает Saga

В классической монолитной архитектуре можно обернуть операции в ACID транзакцию:

@Transactional
public void transferMoney(Account from, Account to, double amount) {
    from.withdraw(amount);  // откатится если случится ошибка
    to.deposit(amount);     // обе операции атомарны
}

Но в микросервисной архитектуре разные сервисы имеют отдельные БД:

  • OrderService — управляет заказами
  • PaymentService — обрабатывает платежи
  • ShippingService — управляет доставкой
  • InventoryService — управляет складом

Если платёж успешен, но доставка недоступна — как откатить платёж? Saga отвечает на этот вопрос.

Две реализации Saga

1. Choreography — через события (разбросанная координация)

Каждый сервис слушает события и выполняет свою часть:

// OrderService инициирует процесс
public class OrderService {
    @Transactional
    public void createOrder(Order order) {
        order.setStatus("PENDING");
        orderRepository.save(order);
        
        // Публикует событие
        eventPublisher.publishEvent(
            new OrderCreatedEvent(order.getId(), order.getAmount())
        );
    }
}

// PaymentService слушает и обрабатывает платёж
@Component
public class PaymentEventListener {
    @EventListener
    @Transactional
    public void onOrderCreated(OrderCreatedEvent event) {
        try {
            processPayment(event.getOrderId(), event.getAmount());
            eventPublisher.publishEvent(
                new PaymentSuccessfulEvent(event.getOrderId())
            );
        } catch (Exception e) {
            eventPublisher.publishEvent(
                new PaymentFailedEvent(event.getOrderId())
            );
        }
    }
}

// ShippingService слушает успешный платёж
@Component
public class ShippingEventListener {
    @EventListener
    @Transactional
    public void onPaymentSuccessful(PaymentSuccessfulEvent event) {
        try {
            createShipment(event.getOrderId());
            eventPublisher.publishEvent(
                new ShipmentCreatedEvent(event.getOrderId())
            );
        } catch (Exception e) {
            // Откатываем платёж!
            eventPublisher.publishEvent(
                new ReversePaymentEvent(event.getOrderId())
            );
        }
    }
}

// При сбое — обратная цепь компенсирующих транзакций
@Component
public class OrderCompensationListener {
    @EventListener
    @Transactional
    public void onPaymentFailed(PaymentFailedEvent event) {
        orderRepository.updateStatus(event.getOrderId(), "FAILED");
    }
}

Плюсы Choreography:

  • Простая реализация
  • Слабая связанность (loose coupling)
  • Легко добавить новые сервисы

Минусы Choreography:

  • Сложно отследить логику (спагетти из событий)
  • Сложно тестировать
  • Трудно понять порядок выполнения

2. Orchestration — через оркестратор (централизованная координация)

Одна сущность (Orchestrator) контролирует весь процесс:

// SagaOrchestrator — центральный контроллер
@Component
public class OrderSagaOrchestrator {
    @Autowired
    private OrderService orderService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private ShippingService shippingService;
    @Autowired
    private InventoryService inventoryService;
    
    @Transactional
    public void executeOrderSaga(Order order) {
        try {
            // Шаг 1: Создаём заказ
            Order createdOrder = orderService.createOrder(order);
            
            // Шаг 2: Обрабатываем платёж
            try {
                paymentService.processPayment(createdOrder.getId(), order.getAmount());
            } catch (PaymentException e) {
                orderService.cancelOrder(createdOrder.getId());
                throw new SagaFailedException("Payment failed", e);
            }
            
            // Шаг 3: Резервируем товар
            try {
                inventoryService.reserveItems(createdOrder.getItems());
            } catch (InventoryException e) {
                paymentService.refund(createdOrder.getId());  // компенсирующая транзакция
                orderService.cancelOrder(createdOrder.getId());
                throw new SagaFailedException("Inventory reservation failed", e);
            }
            
            // Шаг 4: Создаём доставку
            try {
                shippingService.createShipment(createdOrder.getId());
            } catch (ShippingException e) {
                inventoryService.releaseItems(createdOrder.getItems());  // отпускаем товар
                paymentService.refund(createdOrder.getId());              // возвращаем деньги
                orderService.cancelOrder(createdOrder.getId());
                throw new SagaFailedException("Shipment creation failed", e);
            }
            
            // Все успешно!
            orderService.completeOrder(createdOrder.getId());
            
        } catch (SagaFailedException e) {
            log.error("Order saga failed: {}", e.getMessage(), e);
        }
    }
}

Плюсы Orchestration:

  • Логика централизована и понятна
  • Легко отследить поток выполнения
  • Простое тестирование
  • Явное управление компенсирующими транзакциями

Минусы Orchestration:

  • Оркестратор становится "умным" и может быть узким местом
  • Сильная связанность (tight coupling)
  • Оркестратор должен знать о всех сервисах

Saga в Spring Boot с Axon Framework

Saga часто реализуют с помощью фреймворков:

@Saga
public class OrderSaga {
    @Autowired
    private transient CommandGateway commandGateway;
    
    private String orderId;
    private BigDecimal amount;
    
    @StartSaga
    @EventHandler
    public void onOrderCreated(OrderCreatedEvent event) {
        this.orderId = event.getOrderId();
        this.amount = event.getAmount();
        
        // Отправляем команду платёжному сервису
        commandGateway.send(
            new ProcessPaymentCommand(orderId, amount)
        );
    }
    
    @EventHandler
    public void onPaymentProcessed(PaymentProcessedEvent event) {
        // Отправляем команду сервису доставки
        commandGateway.send(
            new CreateShipmentCommand(orderId)
        );
    }
    
    @EventHandler
    public void onPaymentFailed(PaymentFailedEvent event) {
        // Компенсирующая транзакция
        commandGateway.send(
            new CancelOrderCommand(orderId)
        );
    }
    
    @EndSaga
    @EventHandler
    public void onOrderCompleted(OrderCompletedEvent event) {
        // Saga завершена
    }
}

Ключевые концепции

Компенсирующие транзакции — откат операции:

  • Успешно: перевод денег
  • Сбой: возврат денег (refund)
  • Должны быть идемпотентны (безопасны при повторном выполнении)

Идемпотентность критична:

public void refund(String orderId) {
    // Неправильно: может выполниться дважды
    account.addMoney(100);
    
    // Правильно: проверяем, не был ли уже возвращён
    if (refundService.isAlreadyRefunded(orderId)) {
        return;
    }
    account.addMoney(100);
    refundService.markAsRefunded(orderId);
}

Когда использовать Saga

  • Микросервисная архитектура
  • Долгоживущие бизнес-процессы (заказы, платежи)
  • Распределённые транзакции
  • Необходимость откатывать операции

Когда НЕ использовать

  • Простые синхронные операции (используй REST sync вызовы)
  • Когда хватает одной БД (классическая ACID транзакция)
  • Очень короткие операции

Заключение

Saga — это незаменимый паттерн для распределённых систем, позволяющий координировать операции между независимыми сервисами с гарантией финальной консистентности. Выбор между Choreography и Orchestration зависит от сложности процесса и требований к связанности.