Для чего нужен паттерн Saga?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Паттерн 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 зависит от сложности процесса и требований к связанности.