Sagas y transacciones distribuidas | Nicolás Garzón
Una saga coordina un proceso que atraviesa varias fronteras transaccionales mediante pasos locales, estados intermedios y compensaciones. No convierte el proceso en una transacción ACID global.
Cuando cada servicio o contexto posee sus datos, no existe una única transacción que cubra todo el flujo.
Texto
Copiar crear pedido
→ reservar stock
→ autorizar pago
→ confirmar pedidoUna saga mantiene el estado del proceso y define qué hacer cuando un paso falla.
Una operación distribuida puede quedar parcialmente completada:
Texto
Copiar stock reservado
+ pago no autorizadoSin diseño explícito, aparecen datos inconsistentes, recursos bloqueados y acciones manuales improvisadas.
Pasos.
Estados.
Retries.
Timeouts.
Compensaciones.
Resultados irreversibles.
Intervención manual.
Cada participante confirma su propio cambio:
Texto
Copiar Inventory transaction
→ crea reserva
Payments transaction
→ registra autorización
Orders transaction
→ confirma pedidoNo existe rollback automático entre ellos.
Una compensación es una acción de negocio que intenta neutralizar un efecto.
Texto
Copiar reservar stock
↔ liberar reservaNo borra la historia. Debe ser idempotente y puede fallar.
Un correo enviado no puede “desenviarse”. Una transferencia bancaria puede requerir devolución, no rollback.
Cada servicio reacciona a eventos:
Texto
Copiar OrderCreated
→ Inventory reserva
→ StockReserved
→ Payments autoriza
→ PaymentAuthorized
→ Orders confirma
Menos coordinador central.
Participantes autónomos.
Flujo implícito.
Ciclos y cascadas.
Difícil conocer estado global.
Cambios repartidos.
Funciona mejor en procesos simples con pocas etapas y eventos claros.
Un orquestador persiste estado y envía comandos:
Texto
Copiar CheckoutSaga
1. reserveStock
2. authorizePayment
3. confirmOrder
Flujo visible.
Timeouts y compensaciones centralizados.
Diagnóstico más directo.
Coordinador demasiado inteligente.
Acoplamiento a varios contratos.
Punto crítico operacional.
El orquestador debe conocer secuencia, no internals de cada dominio.
TypeScript
Copiar type CheckoutSagaState =
| "started"
| "stock-reserved"
| "payment-authorized"
| "completed"
| "compensating"
| "failed-manual-review" ;
Correlation ID.
IDs de operaciones.
Intentos.
Deadlines.
Resultados por paso.
Versión para concurrencia.
El estado debe persistirse para sobrevivir reinicios.
Orders crea pedido pending.
Saga solicita reserva con request ID.
Inventory responde reserved.
Saga solicita autorización de pago.
Payments responde approved.
Saga confirma pedido.
Publica CheckoutCompleted.
Stock ya está reservado.
Saga solicita ReleaseReservation.
Pedido cambia a payment-failed.
Se conserva auditoría del intento.
No se sabe si se autorizó.
Saga marca payment-unknown.
Consulta estado usando idempotency key.
Espera webhook o reconciliación.
No libera stock hasta resolver o expirar una política segura.
Este caso demuestra por qué timeout no es rechazo.
Cada paso debe clasificar errores:
Transitorio: retry con backoff y jitter.
Negocio: no retry; cambia flujo.
Resultado desconocido: consulta/reconcilia.
Permanente técnico: intervención.
Los retries usan la misma identidad para evitar duplicar efectos.
Una saga necesita deadlines de negocio:
Texto
Copiar reserva expira en 10 minutos
pago pendiente se revisa durante 24 horasUn timeout técnico de 3 segundos no equivale a expiración del proceso.
Reintenta idempotentemente.
Alerta.
Mantiene estado compensating.
Permite intervención.
Reconcilia periódicamente.
La arquitectura debe contemplar el fallo del mecanismo de recuperación.
Mantener invariantes críticas dentro de una sola transacción local.
Separar compromiso temporal de confirmación definitiva.
Para excepciones raras y de alto riesgo puede ser más seguro.
Ofrece coordinación fuerte en entornos controlados, pero añade bloqueo, disponibilidad compuesta y soporte limitado. No es sustituto universal.
El estado y los comandos deben ser idempotentes.
Versiona saga y valida transición.
Optimistic locking o claim evita ejecución concurrente.
Retrásala hasta tener suficiente certeza o diseña reversión de negocio.
El código nuevo debe poder continuar estados históricos o migrarlos.
Sagas por estado.
Edad de procesos pendientes.
Retries y compensaciones.
Fallos por paso.
Tiempo total.
Casos manuales.
Una vista operativa debe permitir buscar por order ID y correlation ID.
Transiciones del state machine.
Duplicados.
Timeouts.
Fallo en cada paso.
Compensación fallida.
Reinicio del orquestador.
Compatibilidad de versiones.
Reconciliación.
Llamar saga a una cadena de llamadas.
Compensaciones no idempotentes.
No persistir estado.
Tratar timeout como rechazo.
Coreografía sin visibilidad.
Prometer atomicidad global.
No diseñar intervención manual.
Acciones irreversibles demasiado pronto.
Proceso largo entre contextos.
Transacciones locales independientes.
Compensaciones o estados pendientes posibles.
Necesidad de continuidad ante fallos.
Operación que puede mantenerse local.
Flujo simple sin necesidad de coordinación.
Dominio donde la consistencia eventual sería inaceptable y no existe compensación segura.
Una saga coordina transacciones locales.
Las compensaciones son acciones nuevas, no rollback.
Timeouts crean incertidumbre.
El estado debe persistirse.
Coreografía y orquestación tienen trade-offs.
La operación manual es parte del diseño.
¿Por qué liberar stock es una compensación y no un rollback?
¿Qué diferencia existe entre timeout técnico y expiración de negocio?
¿Cuándo elegirías orquestación?
¿Qué ocurre si una compensación falla?
¿Qué alternativa usarías si la invariante puede mantenerse local?
CQRS y Event Sourcing separa modelos de escritura y lectura, y estudia cuándo la historia de eventos debe convertirse en la fuente de verdad.