Saga Pattern

Cómo manejar transacciones distribuidas entre microservicios usando coreografía u orquestación con compensaciones.

Qué problema resuelve

En un monolito, una transacción de base de datos puede abarcar múltiples tablas con un simple BEGIN → COMMIT → ROLLBACK. Pero en una arquitectura de microservicios, cada servicio tiene su propia base de datos. No existe una transacción global que pueda hacer rollback en 5 bases de datos diferentes si algo falla en el paso 3.

El Saga Pattern resuelve este problema dividiendo una transacción distribuida en una secuencia de transacciones locales, donde cada paso tiene una acción compensatoria que deshace su efecto si un paso posterior falla.

Ejemplo del problema

Imagina un flujo de compra en un e-commerce:

  1. Servicio de Pedidos: Crear pedido
  2. Servicio de Inventario: Reservar stock
  3. Servicio de Pagos: Cobrar al cliente
  4. Servicio de Envíos: Programar envío

Si el pago falla en el paso 3, necesitas deshacer la reserva de stock (paso 2) y cancelar el pedido (paso 1). Sin un patrón definido, esto se convierte en un caos de llamadas manuales y estados inconsistentes.

Por qué no usar transacciones distribuidas (2PC)

El protocolo Two-Phase Commit (2PC) parece la solución obvia, pero tiene problemas serios en microservicios:

Aspecto2PCSaga
DisponibilidadTodos los participantes deben estar disponibles simultáneamenteCada paso es independiente
Latencias durante toda la transacciónCada paso libera recursos inmediatamente
EscalabilidadLos locks distribuidos limitan el throughputSin locks, cada servicio escala independientemente
AcoplamientoRequiere un coordinador que conozca a todosLos servicios se comunican por eventos
RecuperaciónSi el coordinador falla, los participantes quedan bloqueadosLas compensaciones se ejecutan de forma asíncrona

Cómo funciona

Una saga es una secuencia de transacciones locales donde:

  • Cada transacción actualiza datos dentro de un solo servicio
  • Cada transacción publica un evento o mensaje que dispara la siguiente
  • Si una transacción falla, se ejecutan compensaciones en orden inverso
Flujo exitoso:
  T1 → T2 → T3 → T4 ✅

Flujo con fallo en T3:
  T1 → T2 → T3 ❌ → C2 → C1
  (C = compensación)

Existen dos estrategias principales para coordinar una saga:

Coreografía (Choreography)

Cada servicio escucha eventos y decide qué hacer. No hay un coordinador central.

┌──────────┐    PedidoCreado     ┌──────────────┐
│ Pedidos  │ ──────────────────► │  Inventario  │
└──────────┘                     └──────┬───────┘

                                  StockReservado


                                 ┌──────────────┐
                                 │    Pagos     │
                                 └──────┬───────┘

                                   PagoAprobado


                                 ┌──────────────┐
                                 │   Envíos     │
                                 └──────────────┘

Ventajas: Sin punto único de fallo, servicios desacoplados, fácil agregar participantes. Desventajas: Difícil entender el flujo completo, riesgo de ciclos, difícil depurar.

Orquestación (Orchestration)

Un componente central coordina toda la saga. Sabe qué pasos ejecutar y qué compensaciones aplicar.

                    ┌──────────────────┐
                    │   Orquestador    │
                    │   de Saga        │
                    └──┬───┬───┬───┬──┘
                       │   │   │   │
            Crear      │   │   │   │  Programar
            pedido     │   │   │   │  envío
                       ▼   │   │   ▼
                 ┌────────┐│   │┌────────┐
                 │Pedidos ││   ││Envíos  │
                 └────────┘│   │└────────┘
                    Reservar   │  Cobrar
                    stock  │   │  pago
                           ▼   ▼
                    ┌────────┐┌────────┐
                    │Invent. ││ Pagos  │
                    └────────┘└────────┘

Ventajas: Flujo centralizado, control explícito de compensaciones, fácil depurar. Desventajas: Punto único de fallo potencial, riesgo de God Service, acoplamiento.

Cuándo elegir cada estrategia

| Cquestación | |---|---|---| | Número de pasos | 2-4 pasos simples | 5+ pasos o flujos complejos | | Visibilidad | Aceptable si es simple | Necesaria para flujos críticos | | Equipos | Autónomos e independientes | Equipo central gestiona el flujo | | Compensaciones | Simples y directas | Complejas con lógica condicional | | Monitoreo | Trazabilidad distribuida | Dashboard centralizado |

Transacciones compensatorias

Las compensaciones no son simplemente un “undo”. Son acciones de o que semánticamente revierten el efecto de una transacción anterior:

TransacciónCompensación
Crear pedidoCancelar pedido (cambiar estado, no eliminar)
Reservar stockLiberar stock reservado
Cobrar pagoEmitir reembolso
Programar envíoCancelar programación de envío

Puntos clave sobre compensaciones:

  • No son rollbacks: No borran datos, crean nuevos registros que revierten el efecto
  • Deben ser idempotentes: Ejuce el mismo resultado
  • Pueden fallar: Necesitas reintentos para compensaciones fallidas
  • Mantienen auditoría: Todo queda registrado (acción original y compensación)

Ejemplo práctico: Saga de compra con orquestación

Camino feliz

Orquestador          Pedidos    Inventario    Pagos      Envíos
    │                   │           │           │          │
    │── CrearPedido ───►│           │           │          │
    │◄── PedidoCreado ──│           │           │          │
    │                   │           │           │          │
    │── ReservarStock ──────────────►│           │          │
    │◄── StockReservado ────────────│           │          │
    │                   │           │           │          │
    │── CobrarPago ─────────────────────────────►│          │
    │◄── PagoAprobado ──────────────────────────│          │
    │                   │           │           │          │
    │── ProgramarEnvío ─────────────────────────────────────►│
───────────────────────────│
    │                   │           │           │          │
    ✅ Saga completada

Fallo en el pago (con compensaciones)

Orquestador          Pedidos    Inventario    Pagos
    │                   │           │           │
    │── CrearPedido ───►│           │           │
    │◄── PedidoCreado ──│           │           │
    │                   │           │           │
    │── ReservarStock ──────────────►│           │
    │◄── StockReservado ────────────│           │
    │                   │           │           │
    │── CobrarPago ─────────────────────────────►│
    │◄── PagoRechazado ────────────────────────│  ❌
    │                   │           │           │
    │  ┌── COMPENSACIONES ──────────────────┐  │
    │  │── LiberarStock ────────────►│       │  │
    │  │◄── StockLiberado ──────────│       │  │
    │  │── CancelarPedido ──►│       │       │  │
    │  │◄── PedidoCancelado ─│       │       │  │
    │  └────────────────────────────────────┘  │
             │           │           │
    ❌ Saga compensada

Estado persistido del orquestador

{
  "sagaId": "saga-12345",
  "tipo": "CompraCompleta",
  "estado": "COMPENSANDO",
  "pasoActual": "LiberarStock",
  "datos": {
    "pedidoId": "ped-001",
    "clienteId": "cli-789",
    "monto": 150.00
  },
  "historial": [
    { "paso": "CrearPedido", "estado": "COMPLETADO" },
    { "paso": "ReservarStock", "estado": "COMPLETADO" },
cientes" },
    { "paso": "LiberarStock", "estado": "COMPENSANDO" }
  ]
}

Ventajas

  • Consistencia eventual: Garantiza que el sistema llegará a un estado consistente
  • Autonomía de servicios: Cada servicio mantiene su propia base de datos y transacciones locales
  • Resiliencia: Si un paso falla, las compensaciones restauran la consistencia
  • Escalabilidad: No requiere locks distribuidos ni coordinación síncrona
  • Auditoría natural: Cada paso y compensación queda registrado como evento
  • Flexibilidad: Se pueden agregar o modificar pasos sin afectar todo el flujo

Trade-offs / Desventajas

  • Complejidad: Diseñar compensaciones correctas para cada paso no es trivial
  • Consistencia eventual: El sistema puede estar temporalmente inconsistente
  • Difícil de depurar: Rastrear una saga requiere buenas herramientas de observabilidad
  • Compensaciones imperfectas: No siempre es posible revertir completamente una acción
  • Orden de ejecución: En coreografía, garantizarrecto es complejo
  • Testing: Probar todos los caminos posibles requiere esfuerzo significativo
  • Ventana de inconsistencia: Durante la ejecución, los datos pueden estar en estado parcial

Cuándo usar

  • Transacciones que cruzan múltiples microservicios con bases de datos independientes
  • Flujos de negocio de larga duración (minutos, horas o días)
  • Sistemas donde la consistencia eventual es aceptable
  • Cuando necesitas auditoría completa de cada paso del proceso
  • Flujos con pasos que pueden fallar y necesitan compensación definida
  • Sistemas event-driven donde los servicios ya se comunican por eventos

Cuándo evitar

  • Operaciones que requieren consistencia fuerte e inmediata (ACID)
  • Flujos simples que involucran solo 2 servicios (puede ser overkill)
  • Cuando las compensaciones son imposibles o no tienen sentido
  • Sistemas donde la ventana de inconsistencia no es aceptable
  • Equipos sin experiencia en sistemas distribuidos
  • Cuando un monolito con transacciones locales resuelve el problema

as e implementaciones comunes

CategoríaOpciones
OrquestadoresTemporal, Camunda, AWS Step Functions, Netflix Conductor
MensajeríaApache Kafka, RabbitMQ, Amazon SQS/SNS, Azure Service Bus
FrameworksAxon Framework (Java), MassTransit (.NET), Eventuate Tram
ObservabilidadJaeger, Zipkin, OpenTelemetry (tracing distribuido)
Estado de sagaPostgreSQL, Redis, DynamoDB

Relación con otros patrones

:

┌─────────────────────────────────────────────────┐
│              Ecosistema de la Saga               │
│                                                  │
│  ┌──────────┐    publica     ┌──────────────┐   │
│  │  Saga    │ ──────────────►│   Outbox     │   │
│  │ (pasos)  │    eventos     │  (confiable) │   │
│  └────┬─────┘                └──────────────┘   │
│       │                                          │
│       │ cada paso es                             │
│       │ idempotente                    │
│       ▼                                          │
│  ┌──────────────┐     protege    ┌───────────┐  │
│  │ Idempotency  │ ◄────────────►│  Circuit   │  │
│  │   Pattern    │   llamadas    │  Breaker   │  │
│  └──────────────┘               └───────────┘  │
│                                                  │
│  ┌──────────┐     reintentos    ┌───────────┐  │
│  │  Retry   │ ◄────────────────►│  Timeout  │  │
│  │ Pattern  │   con límite     │  Pattern  │  │
 └───────────┘  │
└─────────────────────────────────────────────────┘
  • Outbox Pattern: Garantiza que los eventos de cada paso se publiquen de forma confiable
  • Idempotency Pattern: Cada paso y compensación debe ser idempotente para reintentos seguros
  • Circuit Breaker: Protege al orquestador cuando un servicio participante está caído
  • Retry Pattern: Reintenta pasos fallidos con backoff antes de activar compensaciones
  • *siderarlo fallido
  • Event-Driven Architecture: La saga se construye sobre un sistema de eventos
  • DDD: Los bounded contexts definen los límites de cada participante

Errores comunes

ErrorConsecuenciaSolución
Compensaciones no idempotentesDuplicación de reembolsosUsar IDs de idempotencia
No persistir estado de la sagaSagas perdidas tras reinicioGuardar estado en BD durable
Ignorar fallos en compener queue
Saga con demasiados pasosComplejidad inmanejableDividir en sub-sagas
No monitorear sagas activasSagas atascadas sin detectarDashboard con alertas

Próximos pasos

Una plora el Idempotency Pattern para asegurar que los pasos de la saga sean seguros ante reintentos.