1. Planteamiento
Después de que un usuario envía un pedido, los servicios de inventario, pago, entrega y cupones deben completar el cumplimiento. Cualquier paso puede fallar debido a una regla de negocio o a un fallo de red, y una transacción remota ya confirmada no se puede revertir mediante rollback. Diseñe una Saga que termine en CONFIRMED o CANCELLED, explicando cada transacción local y su compensación.
2. Restricciones y aclaraciones
- Cada servicio ejecuta una transacción ACID local únicamente en su propia base de datos.
- La consistencia eventual es aceptable, pero las retenciones de inventario y pago no pueden permanecer para siempre.
- Los reintentos pueden ejecutar un paso más de una vez; los servicios deben proteger el estado con claves de idempotencia.
- Distinga entre fallos técnicos reintentables, rechazos de negocio no reintentables y fallos de compensación que requieran intervención humana.
3. Enfoque principal
Una Saga descompone una transacción larga en transacciones locales ordenadas T1 ... Tn. Cada paso exitoso registra el progreso y desencadena el siguiente; si un paso posterior falla, los pasos completados ejecutan compensaciones Ck ... C1 en orden inverso. Una compensación es una nueva operación de negocio, como liberar inventario, anular una autorización de pago, cancelar una entrega o devolver un cupón, en lugar de un rollback a nivel de base de datos.
Una Saga orquestada cuenta con un coordinador duradero que almacena el estado y la siguiente acción, lo que se adapta bien a requisitos explícitos de flujo de trabajo, tiempos de espera e intervención humana. La coreografía basada en eventos elimina el coordinador central, pero dificulta la visibilidad y el control de ciclos. En cualquiera de los dos estilos, incluya un saga_id, step_id, versión y clave de idempotencia en cada comando y evento.
4. Implementación de referencia
start(order):
saga = create_saga(order.id, state="RESERVE_STOCK")
dispatch(saga, "ReserveStock")
on_step_result(saga_id, step_id, result):
saga = load_and_lock(saga_id)
require result.version == saga.version + 1
if result.success:
saga.completed_steps.append(step_id)
saga.version += 1
next = next_step(saga)
persist(saga)
dispatch(next) if next else finish_confirmed(saga)
else if result.business_rejection:
saga.state = "COMPENSATING"
persist(saga)
dispatch(compensation_for_last_completed(saga))
else:
schedule_retry_or_timeout(saga, step_id)
on_compensation_result(saga_id, step_id, result):
record_attempt(saga_id, step_id, result)
if result.success:
dispatch(previous_compensation(saga))
else:
mark_manual_intervention(saga, reason=result.error)5. Consistencia y corrección
El estado del coordinador debe ser duradero; de lo contrario, una caída puede perder la siguiente acción. Cada comando y evento utiliza una clave de idempotencia; el consumidor registra un step_id procesado antes de confirmar su cambio de negocio, lo que evita que los reintentos reserven inventario dos veces. También existe una brecha de envío entre completar un paso y publicar el siguiente comando, por lo que se necesita un outbox, una cola confiable o CDC para lograr visibilidad eventual.
La compensación generalmente se ejecuta en orden inverso al éxito, pero no toda acción tiene un inverso estricto. Algunos efectos requieren un ajuste de negocio, como un reembolso en lugar de retractar una notificación ya enviada. Las consultas de estado deben exponer el paso actual, los pasos completados, el recuento de reintentos y el motivo de la intervención humana, en lugar de reportar "en procesamiento" como éxito.
6. Preguntas de seguimiento y trampas
- No describa la Saga como un rollback atómico entre bases de datos; proporciona consistencia eventual recuperable.
- La compensación también puede fallar, por lo que debe añadir reintentos, colas de mensajes no procesables (dead letters), alertas y toma de control humana en lugar de un bucle automático infinito.
- Un bloqueo distribuido global expande el dominio de fallos y no puede deshacer una confirmación de negocio remota.
- Defina TTLs para las retenciones de inventario y pago; libérelas con un temporizador o evento cuando la Saga expire.
7. Lecturas adicionales
Compare la orquestación con la coreografía: la orquestación centraliza el estado, el orden y los tiempos de espera, mientras que la coreografía desacopla los servicios mediante eventos pero dificulta el rastreo de extremo a extremo. Analice los efectos secundarios no compensables, los bloqueos semánticos, los conflictos de versión, los registros de auditoría y los casos en los que una sola transacción de base de datos resulta más simple.
8. Criterios de evaluación en entrevistas
Capacidad para descomponer transacciones locales
El candidato debe enumerar los pasos de inventario, pago, entrega y cupón, y especificar que cada servicio confirma únicamente su propia transacción local.
Capacidad para diseñar una máquina de estados de compensación
Debe mostrar las rutas de éxito, fallo y compensación inversa, distinguiendo el rechazo de negocio, el reintento técnico y la toma de control humana.
Capacidad para manejar la idempotencia y la mensajería confiable
Debe utilizar saga_id, step_id, claves de idempotencia y un outbox o cola confiable para cubrir caídas del coordinador y brechas en el envío de comandos.
Capacidad para definir límites de negocio
Debe reconocer que la compensación no es un rollback y discutir TTLs, efectos secundarios no compensables, consultas de estado y la experiencia de usuario ante la consistencia eventual.