Command Palette

Search for a command to run...

ES·EN

Nivel 4 · 35 min

Patrón Saga

El patrón Saga gestiona transacciones distribuidas en sistemas de microservicios donde una transacción ACID distribuida no es posible. Una saga es una secuencia de transacciones locales donde cada transacción publica eventos o mensajes que disparan el siguiente paso.

Coreografía vs Orquestación

En Coreografía, no hay coordinador central —cada servicio reacciona a eventos y publica eventos para disparar el siguiente paso. Altamente desacoplado pero difícil de rastrear el flujo completo. En Orquestación, hay un Saga Orchestrator central que envía commands a cada servicio y espera respuestas. Más fácil de rastrear, pero el orquestador es un punto central de coordinación.

Transacciones Compensatorias

Cuando un paso falla después de que pasos anteriores ya committearon, la saga ejecuta transacciones compensatorias —operaciones semánticas de deshacer. ReserveInventory → compensación: ReleaseInventory. ChargePayment → compensación: RefundPayment. Las compensaciones no son ACID rollback —algunos efectos no se pueden deshacer (email enviado, SMS recibido).

Manejo de Fallos

Los fallos en una saga son complejos: ¿qué pasa si la compensación también falla? Estrategias: retry con backoff exponencial para fallos transitorios, dead letter queue para fallos persistentes, estado de saga en tabla dedicada para reintentar manualmente, idempotencia en todos los pasos (mismo command dos veces = mismo resultado).

Puntos clave

  • Las sagas reemplazan transacciones ACID distribuidas con consistencia eventual mediante compensaciones.
  • Las transacciones compensatorias son semánticas, no ACID —algunos efectos no se pueden deshacer.
  • Todos los pasos de una saga deben ser idempotentes para soportar retry seguro.

Code example

// Saga con orquestador
class PlaceOrderSaga {
  enum State { STARTED, INVENTORY_RESERVED, PAYMENT_CHARGED,
               INVENTORY_RELEASED, PAYMENT_REFUNDED, COMPLETED, FAILED }

  void start(PlaceOrderCommand cmd) {
    inventoryService.reserveItems(cmd.items());
  }

  @EventHandler
  void on(InventoryReserved event) {
    state = INVENTORY_RESERVED;
    paymentService.charge(event.orderId(), orderTotal);
  }

  @EventHandler
  void on(PaymentFailed event) {
    // Compensación: liberar el inventario reservado
    inventoryService.releaseItems(event.orderId());
    state = INVENTORY_RELEASED;
  }

  @EventHandler
  void on(InventoryReleased event) {
    state = FAILED;
  }
}