En un monolito con una sola base de datos, mantener la coherencia es sencillo: envuelves todo en una transacción ACID y, si algo falla, haces ROLLBACK y es como si nada hubiera pasado. Pero en una arquitectura de microservicios cada servicio tiene su propia base de datos, y una operación de negocio (como "tramitar un pedido") abarca a varios de ellos. No existe un ROLLBACK global que abarque bases de datos distintas y servicios remotos. ¿Cómo garantizamos entonces la coherencia de una operación que toca pedidos, pagos, inventario y envíos?
La respuesta es el patrón Saga: dividimos la transacción distribuida en una secuencia de transacciones locales, cada una en un servicio, coordinadas mediante eventos o mensajes. Si un paso falla, en lugar de un rollback ejecutamos transacciones de compensación que deshacen semánticamente los pasos anteriores. En esta lección estudiaremos las dos formas de implementar sagas (coreografía y orquestación), el concepto de compensación y un ejemplo completo de pedido.
Contenido
- El problema de las transacciones distribuidas
- Qué es una Saga y las transacciones de compensación
- Saga por coreografía
- Saga por orquestación
- Comparativa coreografía vs orquestación
- Ejemplo completo: tramitar un pedido (con diagrama)
- Errores comunes y consejos
- Ejercicios y soluciones
- Conclusión
- El problema de las transacciones distribuidas
Imagina el flujo "tramitar pedido":
- Pedidos crea el pedido.
- Pagos cobra al cliente.
- Inventario reserva el stock.
- Envíos programa el reparto.
Si el paso 3 falla (no hay stock), el cobro del paso 2 ya se ha realizado. No podemos hacer un ROLLBACK distribuido porque:
- Cada servicio tiene su propia BD; no comparten transacción.
- Las soluciones clásicas como el Two-Phase Commit (2PC) bloquean recursos, escalan mal y crean un fuerte acoplamiento. En la práctica casi nadie las usa en microservicios.
La saga acepta una verdad incómoda: en sistemas distribuidos buscamos consistencia eventual, no consistencia inmediata. Habrá un instante en que el cobro está hecho pero el stock no reservado; la saga garantiza que el sistema acabe en un estado coherente (todo confirmado o todo compensado).
- Qué es una Saga y las transacciones de compensación
Una saga es una secuencia de transacciones locales T1, T2, ..., Tn. Cada Ti tiene una transacción de compensación Ci que deshace su efecto. Si T1, T2, T3 tienen éxito pero T4 falla, la saga ejecuta C3, C2, C1 en orden inverso.
Aspectos cruciales de las compensaciones:
- No es un rollback técnico, sino semántico. No "borras" el cobro; emites un reembolso. El historial queda (importante para auditoría).
- Deben ser idempotentes y no poder fallar (o tener reintentos robustos), porque si la compensación falla, el sistema queda inconsistente.
- Algunas acciones no se pueden compensar (enviar un email ya enviado). Para esas, se reordena la saga para ejecutarlas al final, cuando ya no hay vuelta atrás (pivot transaction).
| Transacción (Ti) | Compensación (Ci) |
|---|---|
| Cobrar al cliente | Reembolsar al cliente |
| Reservar stock | Liberar la reserva |
| Crear pedido | Cancelar pedido |
- Saga por coreografía
En la coreografía no hay coordinador central. Cada servicio escucha eventos, hace su trabajo y emite un nuevo evento que dispara al siguiente. El flujo "emerge" de las reacciones encadenadas (es la topología de broker de la lección 05-01).
flowchart LR
P[Pedidos] -->|PedidoCreado| Pay[Pagos]
Pay -->|PagoConfirmado| Inv[Inventario]
Inv -->|StockReservado| Env[Envíos]
Inv -.->|StockAgotado| Pay
Pay -.->|PagoReembolsado| P- Camino feliz (líneas continuas): cada evento dispara al siguiente servicio.
- Camino de fallo (líneas punteadas): si Inventario emite
StockAgotado, Pagos reacciona reembolsando y Pedidos cancela. Las compensaciones también viajan como eventos.
Ventajas: máximo desacoplamiento, sin punto único de fallo. Inconvenientes: el flujo global no está en ningún sitio; con muchos pasos se vuelve difícil de seguir y depurar (riesgo de "dependencias cíclicas" de eventos).
- Saga por orquestación
En la orquestación, un componente central —el orquestador— dirige la saga: envía comandos a cada servicio, espera la respuesta y decide el siguiente paso o lanza las compensaciones (es la topología de mediador).
// Orquestador de la saga "Tramitar Pedido"
@Component
public class OrquestadorTramitarPedido {
public void tramitar(String pedidoId) {
var saga = new EstadoSaga(pedidoId);
try {
// Cada paso es una transacción local en otro servicio
saga.marcar("PEDIDO_CREADO", pedidos.crear(pedidoId));
saga.marcar("PAGO_HECHO", pagos.cobrar(pedidoId));
saga.marcar("STOCK_RESERVADO", inventario.reservar(pedidoId));
saga.marcar("ENVIO_PROGRAMADO",envios.programar(pedidoId));
saga.completar();
} catch (PasoSagaException ex) {
// Si algo falla, compensamos en orden INVERSO
compensar(saga);
}
}
private void compensar(EstadoSaga saga) {
if (saga.hizo("STOCK_RESERVADO")) inventario.liberar(saga.pedidoId());
if (saga.hizo("PAGO_HECHO")) pagos.reembolsar(saga.pedidoId());
if (saga.hizo("PEDIDO_CREADO")) pedidos.cancelar(saga.pedidoId());
}
}Explicación detallada:
- El orquestador conoce todo el flujo: el orden de los pasos y sus compensaciones. Esto centraliza la lógica del proceso en un único sitio legible.
EstadoSagaregistra qué pasos se completaron. Es esencial persistirlo (en BD), porque si el orquestador se reinicia a mitad debe saber por dónde iba para reanudar o compensar.- En
compensar, solo deshacemos los pasos que sí se ejecutaron, en orden inverso: primero liberar stock, luego reembolsar, luego cancelar el pedido. - En producción, las llamadas suelen ser asíncronas (comandos por cola y respuestas por eventos) en lugar de invocaciones bloqueantes, pero la lógica de coordinación es la misma.
- Comparativa coreografía vs orquestación
| Criterio | Coreografía | Orquestación |
|---|---|---|
| Coordinación | Distribuida (eventos) | Centralizada (orquestador) |
| Acoplamiento | Mínimo | Medio (todos dependen del orquestador) |
| Visibilidad del flujo | Baja (repartido) | Alta (en un sitio) |
| Facilidad de depuración | Difícil | Fácil |
| Punto único de fallo | No | Sí (el orquestador) |
| Riesgo | Dependencias cíclicas de eventos | Orquestador "dios" sobrecargado |
| Ideal para | Pocos pasos (2-4), desacople máximo | Flujos largos y complejos |
Regla práctica: usa coreografía para sagas cortas y simples; usa orquestación cuando el proceso tenga muchos pasos, lógica condicional o necesites visibilidad y control claros.
- Ejemplo completo: tramitar un pedido (con diagrama)
Diagrama de secuencia de la saga orquestada, incluyendo el camino de compensación cuando falta stock:
sequenceDiagram
participant O as Orquestador
participant P as Pedidos
participant Pay as Pagos
participant I as Inventario
O->>P: crear(pedido)
P-->>O: PedidoCreado
O->>Pay: cobrar(pedido)
Pay-->>O: PagoConfirmado
O->>I: reservar(stock)
I-->>O: StockAgotado (FALLO)
Note over O: Inicia compensación en orden inverso
O->>Pay: reembolsar(pedido)
Pay-->>O: PagoReembolsado
O->>P: cancelar(pedido)
P-->>O: PedidoCanceladoObserva que no hay reserva de stock que liberar (ese paso falló), por lo que la compensación arranca directamente en el reembolso del pago y termina cancelando el pedido. El sistema queda en un estado coherente: el cliente no ha pagado y no tiene pedido.
Un detalle clave de implementación: cada compensación debe ser idempotente. Si el orquestador reintenta reembolsar(pedido) porque no recibió respuesta, el servicio de Pagos debe usar la clave de idempotencia (lección 05-02) para no reembolsar dos veces.
Errores Comunes y Consejos
- Compensaciones que pueden fallar y no se reintentan. Si una compensación falla y no hay reintentos, el sistema queda inconsistente sin remedio. Hazlas idempotentes y con reintentos persistentes.
- No persistir el estado de la saga. Si el orquestador se reinicia y no sabe qué pasos hizo, no puede compensar correctamente. El estado de la saga debe sobrevivir a caídas.
- Intentar compensar acciones irreversibles. Un email enviado no se "des-envía". Coloca esas acciones al final de la saga (tras la pivot transaction) o emite una acción correctora (un email de disculpa).
- Confundir saga con 2PC. La saga no ofrece aislamiento: durante su ejecución, otros pueden ver estados intermedios. Hay que diseñar contando con ello (estados "PENDIENTE", reservas temporales, etc.).
- Consejo: modela explícitamente cada estado intermedio del pedido (
PENDIENTE_PAGO,PAGADO,CANCELADO) para que la inconsistencia temporal sea visible y controlada, no accidental.
Ejercicios
- Define las transacciones de compensación para esta saga de reserva de viaje: T1 reservar vuelo, T2 reservar hotel, T3 alquilar coche. Si T3 falla, ¿qué compensaciones se ejecutan y en qué orden?
- Para el flujo "alta de póliza de seguro" (validar datos, tarificar, cobrar prima, emitir póliza, enviar email de bienvenida) decide si usarías coreografía u orquestación y dónde colocarías el envío del email. Justifica.
- Explica por qué las transacciones de compensación deben ser idempotentes, usando como ejemplo un reembolso de pago en una saga con reintentos.
Soluciones
- Compensaciones: C1 cancelar vuelo, C2 cancelar hotel, C3 cancelar coche. Si T3 falla, no hay coche que cancelar; se compensan los pasos previos en orden inverso: primero C2 (cancelar hotel) y luego C1 (cancelar vuelo).
- Orquestación, porque es un flujo largo con varios pasos y lógica condicional (la tarificación y el cobro pueden rechazar), y se necesita visibilidad/control para un proceso regulado. El email de bienvenida va al final, como acción no compensable tras la pivot transaction (emitir póliza): solo se envía cuando ya no hay marcha atrás, evitando tener que "des-enviar" un correo si algo falla antes.
- Con reintentos (at-least-once), el orquestador puede invocar
reembolsar(pedido)más de una vez si no recibe confirmación. Si la compensación no fuera idempotente, reembolsaríamos al cliente dos o más veces. Usando la clave de idempotencia del pago, el servicio detecta que ese reembolso ya se hizo y lo ignora, garantizando un único reembolso pese a los reintentos.
Conclusión
El patrón Saga resuelve el problema de las transacciones distribuidas descomponiéndolas en transacciones locales coordinadas, con compensaciones semánticas que deshacen el trabajo cuando un paso falla. Distinguimos la coreografía (eventos encadenados, máximo desacoplamiento) de la orquestación (coordinador central, máxima visibilidad), y vimos que la idempotencia de las compensaciones es innegociable. Asumimos, además, que ganamos coherencia eventual a cambio de tolerar estados intermedios.
En la siguiente lección, "Streaming de Datos en Tiempo Real", cerramos el módulo viendo cómo procesar flujos continuos de eventos —no ya operaciones individuales— para analítica y reacción en tiempo real con herramientas como Kafka Streams.
Curso de Arquitectura de Aplicaciones
Módulo 1: Fundamentos de la Arquitectura de Aplicaciones
- ¿Qué es la Arquitectura de Aplicaciones?
- El Rol del Arquitecto de Software
- Atributos de Calidad y Requisitos No Funcionales
- Decisiones Arquitectónicas y Compromisos (Trade-offs)
- Documentación de Arquitectura: Vistas y el Modelo C4
Módulo 2: Principios y Tácticas de Diseño
- Acoplamiento, Cohesión y Separación de Responsabilidades
- Principios SOLID Aplicados a la Arquitectura
- DRY, KISS, YAGNI y Otros Principios de Diseño
- Tácticas Arquitectónicas para los Atributos de Calidad
- Gestión de la Deuda Técnica
Módulo 3: Estilos y Patrones Arquitectónicos
- Arquitectura Monolítica
- Arquitectura en Capas (N-Tier)
- Arquitectura Cliente-Servidor
- Arquitectura Hexagonal (Puertos y Adaptadores)
- Arquitectura Limpia y Cebolla (Clean & Onion)
Módulo 4: Arquitecturas Distribuidas y Microservicios
- Introducción a los Sistemas Distribuidos
- Arquitectura de Microservicios
- Descomposición de Servicios y Bounded Contexts
- API Gateway, Service Discovery y Comunicación entre Servicios
- Patrones de Resiliencia: Circuit Breaker, Retry y Bulkhead
- El Teorema CAP y la Consistencia de Datos
Módulo 5: Arquitecturas Dirigidas por Eventos y Mensajería
- Fundamentos de la Arquitectura Orientada a Eventos
- Mensajería Asíncrona: Colas y Brokers
- Patrones de Eventos: Event Sourcing y CQRS
- Gestión de Transacciones Distribuidas: Patrón Saga
- Streaming de Datos en Tiempo Real
Módulo 6: Diseño Dirigido por el Dominio (DDD)
- Conceptos Fundamentales del DDD
- Diseño Estratégico: Bounded Contexts y Lenguaje Ubicuo
- Diseño Táctico: Entidades, Agregados y Repositorios
- Mapeo de Contextos (Context Mapping)
Módulo 7: Datos y Persistencia
- Estrategias de Persistencia: SQL vs NoSQL
- Patrones de Acceso a Datos: Repository, Unit of Work y DAO
- Base de Datos por Servicio y Gestión de Datos Distribuidos
- Caché y Estrategias de Invalidación
Módulo 8: Arquitectura en la Nube y Despliegue
- Fundamentos del Cloud Computing (IaaS, PaaS, SaaS)
- Contenedores y Orquestación con Docker y Kubernetes
- Arquitectura Serverless
- Patrones de Diseño Cloud-Native
- Infraestructura como Código (IaC)
Módulo 9: Calidad, Seguridad y Observabilidad
- Escalabilidad: Horizontal vs Vertical y Balanceo de Carga
- Alta Disponibilidad y Tolerancia a Fallos
- Seguridad por Diseño y Autenticación/Autorización
- Observabilidad: Logging, Métricas y Trazabilidad
- Rendimiento y Pruebas de Carga
