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

Что такое паттерн SAGA?

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

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

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

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

SAGA паттерн: управление распределенными транзакциями

SAGA — это паттерн для управления долгоживущими транзакциями в микросервисной архитектуре, когда нет единой ACID базы данных.

Проблема: ACID транзакции в микросервисах

Одна БД (монолит) → одна транзакция ✓
Multiple DBs (микросервисы) → как откатить все, если что-то упадет?

Пример:
Пользователь заказывает билет:
1. Reserve ticket in TicketService
2. Charge payment in PaymentService
3. Send confirmation email in NotificationService

Что если:
- Билет зарезервирован ✓
- Платеж упал ❌
- Что делать с резервой?

Традиционная ACID транзакция здесь невозможна, потому что это разные сервисы и БД.

SAGA решение

SAGA — это последовательность локальных транзакций, где каждая база может откатить свои изменения (компенсирующая транзакция).

Транзакция 1: Reserve ticket     ✓ Откат: Cancel reservation
Транзакция 2: Charge payment     ✓ Откат: Refund money
Транзакция 3: Send email         ✓ Откат: Send cancellation email

Два типа SAGA

1. Choreography (Танец без дирижера)

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

OrderService: "Заказ создан" → Публикует OrderCreatedEvent
  ↓
TicketService слушает OrderCreatedEvent
  → Резервирует билет
  → Публикует TicketReservedEvent
  ↓
PaymentService слушает TicketReservedEvent
  → Берет платеж
  → Публикует PaymentChargedEvent
  ↓
NotificationService слушает PaymentChargedEvent
  → Отправляет email

Если PaymentService падает:

PaymentService: "Платеж упал" → Публикует PaymentFailedEvent
  ↓
TicketService слушает PaymentFailedEvent
  → Отменяет резервацию (компенсирующая транзакция)
  → Публикует TicketReleasedEvent

На Java с Spring Cloud Stream:

// OrderService
@Service
public class OrderService {
  private final OrderRepository orderRepo;
  private final StreamBridge streamBridge;
  
  public void createOrder(Order order) {
    orderRepo.save(order);
    
    // Публикуем событие
    streamBridge.send(
      "order-events",
      new OrderCreatedEvent(order.getId(), order.getCustomerId())
    );
  }
}

// TicketService
@Service
public class TicketService {
  private final TicketRepository ticketRepo;
  private final StreamBridge streamBridge;
  
  @Bean
  public Consumer<OrderCreatedEvent> reserveTicket() {
    return event -> {
      try {
        // 1. Локальная транзакция
        TicketReservation reservation = new TicketReservation();
        reservation.setOrderId(event.getOrderId());
        reservation.setStatus("RESERVED");
        ticketRepo.save(reservation);
        
        // 2. Публикуем успех
        streamBridge.send(
          "ticket-events",
          new TicketReservedEvent(event.getOrderId())
        );
      } catch (Exception e) {
        // 3. Публикуем ошибку → другие сервисы откатятся
        streamBridge.send(
          "ticket-events",
          new TicketReservationFailedEvent(event.getOrderId())
        );
      }
    };
  }
  
  @Bean
  public Consumer<PaymentFailedEvent> releaseTicket() {
    return event -> {
      // Компенсирующая транзакция
      TicketReservation reservation = ticketRepo.findByOrderId(event.getOrderId());
      reservation.setStatus("RELEASED");
      ticketRepo.save(reservation);
    };
  }
}

// PaymentService
@Service
public class PaymentService {
  private final PaymentRepository paymentRepo;
  private final StripeClient stripeClient; // Платежный шлюз
  private final StreamBridge streamBridge;
  
  @Bean
  public Consumer<TicketReservedEvent> chargePayment() {
    return event -> {
      try {
        // 1. Локальная транзакция в своей БД
        Payment payment = new Payment();
        payment.setOrderId(event.getOrderId());
        payment.setAmount(event.getAmount());
        paymentRepo.save(payment);
        
        // 2. Внешняя система (Stripe)
        stripeClient.charge(payment.getAmount(), event.getCustomerId());
        
        // 3. Успех
        streamBridge.send(
          "payment-events",
          new PaymentChargedEvent(event.getOrderId())
        );
      } catch (Exception e) {
        // 4. Ошибка → откат
        streamBridge.send(
          "payment-events",
          new PaymentFailedEvent(event.getOrderId())
        );
      }
    };
  }
  
  @Bean
  public Consumer<TicketReservationFailedEvent> refundPayment() {
    return event -> {
      // Компенсирующая транзакция
      Payment payment = paymentRepo.findByOrderId(event.getOrderId());
      stripeClient.refund(payment.getAmount());
      payment.setStatus("REFUNDED");
      paymentRepo.save(payment);
    };
  }
}

2. Orchestration (Дирижер управляет)

Центральный Orchestrator (SAGA) управляет всеми шагами:

OrderSagaOrchestrator:
  Step 1: Call TicketService.reserveTicket()
    ✓ Success → Step 2
    ❌ Fail → Compensate
  
  Step 2: Call PaymentService.chargePayment()
    ✓ Success → Step 3
    ❌ Fail → Compensate Step 1 + Step 2
  
  Step 3: Call NotificationService.sendEmail()
    ✓ Success → Done
    ❌ Fail → Compensate все

На Java с Temporal или Spring State Machine:

@Component
public class OrderSaga {
  private final TicketServiceClient ticketService;
  private final PaymentServiceClient paymentService;
  private final NotificationServiceClient notificationService;
  
  @Transactional
  public void executeOrder(OrderRequest request) {
    Order order = new Order();
    order.setStatus("PENDING");
    
    try {
      // Шаг 1: Зарезервировать билет
      TicketReservation ticket = ticketService.reserve(
        request.getTicketId()
      );
      order.setTicketReservationId(ticket.getId());
      
      // Шаг 2: Взять платеж
      Payment payment = paymentService.charge(
        request.getAmount(),
        request.getPaymentMethod()
      );
      order.setPaymentId(payment.getId());
      
      // Шаг 3: Отправить email
      notificationService.sendConfirmation(request.getEmail());
      
      order.setStatus("COMPLETED");
      
    } catch (PaymentException e) {
      // Компенсирующие транзакции (в обратном порядке)
      ticketService.cancel(order.getTicketReservationId());
      order.setStatus("FAILED_REFUNDED");
      
    } catch (TicketException e) {
      order.setStatus("FAILED");
      
    } catch (Exception e) {
      // Откатить всё
      ticketService.cancel(order.getTicketReservationId());
      paymentService.refund(order.getPaymentId());
      notificationService.sendCancellation(request.getEmail());
      order.setStatus("FAILED_COMPENSATED");
    }
  }
}

Choreography vs Orchestration

Choreography (Танец):
✓ Каждый сервис независим
✗ Сложно отследить логику (разреженная по всем сервисам)
✗ Сложно тестировать
✗ Cyclic dependencies возможны

Orchestration (Дирижер):
✓ Центральная логика (легко понять)
✓ Легче отследить ошибки
✓ Проще тестировать
✗ Зависимость от Orchestrator
✗ Single point of failure

Вызовы SAGA

  1. Идемпотентность — каждый вызов должен быть безопасен при повторении
// Если платеж упал но деньги ушли, повтор не должен взять еще раз
public PaymentResponse charge(String idempotencyKey, BigDecimal amount) {
  // Проверяю: был ли уже такой платеж?
  Payment existing = paymentRepo.findByIdempotencyKey(idempotencyKey);
  if (existing != null) {
    return existing.getResponse(); // Возвращаю старый результат
  }
  // Иначе новый платеж
}
  1. Временные ограничения — компенсирующая транзакция может не быть возможна
Если 30 дней прошли, не можно вернуть билет
→ Нужна сложная логика (деньги на счет, но не возврат)
  1. Monitoring — нужно отслеживать статус SAGA

Выводы

  1. SAGA = паттерн для распределенных транзакций
  2. Использует событие для координации
  3. Choreography когда сервисы независимы
  4. Orchestration когда нужна централизованная логика
  5. Требует идемпотентности и компенсирующих транзакций
  6. Отлично подходит для микросервисов и асинхронных процессов