Diseña un pipeline de procesamiento de pedidos orientado a eventos, desde que se coloca el pedido hasta su cumplimiento
Un solo pedido toca cuatro servicios desplegados de forma independiente y ninguna transacción distribuida — esta es la pregunta de entrevista que revela si de verdad entiendes las sagas, no solo los brokers de mensajería.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
Diseña un pipeline de procesamiento de pedidos orientado a eventos, desde que se coloca el pedido hasta su cumplimiento
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Por qué esta es la pregunta más difícil del módulo
Cada lección anterior de este módulo trató una sola pieza — un broker, un esquema, un fan-out, un pipeline de captura. Esta te pide ensamblar esas piezas en un sistema que atraviesa cuatro servicios —Pedidos, Inventario, Pagos, Envíos— cada uno con su propia base de datos, su propio ciclo de despliegue, su propio equipo, y sin ninguna transacción compartida entre ellos. No puedes envolver "reservar inventario, cobrar la tarjeta, programar un envío" en una sola transacción ACID como podrías hacerlo con cuatro tablas en una sola base de datos. La entrevista en realidad evalúa si recurres al patrón correcto aquí: una saga —una secuencia de transacciones locales, cada una en un servicio, coordinadas mediante eventos, con una transacción compensatoria explícita definida para cada paso que pueda necesitar deshacerse.
#Coreografía vs. orquestación
Hay dos formas de coordinar una saga, y una buena respuesta nombra ambas y elige una con una razón:
-
Coreografía — cada servicio reacciona a los eventos de los demás y a su vez emite sus propios eventos; no hay un coordinador central. Pedidos publica
OrderPlaced; Inventario escucha, reserva stock, publicaInventoryReserved; Pagos escucha eso, cobra la tarjeta, publicaPaymentCharged; y así sucesivamente. Cada servicio solo necesita conocer la forma de los eventos que produce y consume — nada más. Esto escala bien organizacionalmente (los servicios permanecen desacoplados), pero el flujo completo solo existe de forma implícita, repartido en el código de N servicios — reconstruir "en qué estado está el pedido #4521 ahora mismo" significa correlacionar eventos entre los logs de todos los servicios. -
Orquestación — un orquestador de saga dedicado (una máquina de estados, a menudo implementada literalmente con algo como AWS Step Functions, Temporal, o una tabla de estados hecha a mano) llama explícitamente a cada servicio en secuencia, rastrea el estado actual de la saga, y decide qué acción compensatoria disparar ante una falla. Esto hace que el estado del flujo sea explícito y consultable en un solo lugar, a cambio de un nuevo componente con el que cada servicio debe integrarse, y una pieza central de lógica de coordinación que puede convertirse ella misma en un cuello de botella para pasos nuevos.
Esta lección construye el pipeline al estilo coreografía, ya que es la forma más clara de mostrar el flujo de eventos en sí, y señala dónde un orquestador cambiaría el panorama.
#El camino feliz, evento por evento
OrderPlaced— el servicio de Pedidos valida la solicitud (artículos, direcciones, precios) y, dentro de la misma transacción local que inserta la fila del pedido, escribe una fila de outbox (ver la lección de CDC/outbox de este módulo) para que la publicación esté garantizada de coincidir con lo que realmente se confirmó. Estado del pedido:PENDING.InventoryReserved— el servicio de Inventario consumeOrderPlaced, intenta reservar stock para cada línea del pedido en una sola transacción local, y si tiene éxito publicaInventoryReserved. Es una reserva, no un descuento — el stock queda retenido, no descontado permanentemente, hasta que el pedido se confirme.PaymentCharged— el servicio de Pagos consumeInventoryReserved, cobra el método de pago del cliente (a través de una pasarela de pago, ella misma idempotente por intento), y publicaPaymentChargedsi tiene éxito.ShippingScheduled— el servicio de Envíos consumePaymentCharged, reserva una recolección/etiqueta con la transportadora, y publicaShippingScheduled.OrderFulfilled— el servicio de Pedidos consumeShippingScheduledy pasa el pedido a su estado terminal de éxito, que es lo que finalmente hace visible el pedido al cliente como confirmado de punta a punta.
#Dónde sale mal — y las compensaciones que lo deshacen
Esta es la parte que la mayoría de los candidatos se salta, y la que realmente demuestra entendimiento de sagas: cada paso que cambió estado necesita una acción explícita y opuesta, porque ningún motor de base de datos te lo va a revertir por ti.
| Paso que falla | Acción(es) compensatoria(s) requerida(s) | Estado resultante del pedido |
|---|---|---|
| Falla la reserva de inventario (stock insuficiente) | Nada confirmado todavía que deshacer — Inventario publica InventoryReservationFailed; Pedidos lo consume directamente | REJECTED |
| El pago es rechazado | Inventario debe liberar su reserva: Pagos publica PaymentFailed → Inventario lo consume, libera el stock reservado, publica InventoryReleased → Pedidos lo consume | CANCELLED |
| No se puede programar el envío (caída de la transportadora, dirección inválida detectada tarde) | Debe reembolsarse el pago, y luego liberarse el inventario: Envíos publica ShippingFailed → Pagos lo consume, emite un reembolso, publica PaymentRefunded → Inventario lo consume, libera el stock, publica InventoryReleased → Pedidos lo consume | CANCELLED (reembolsado) |
| Un paso nunca responde (timeout) | El servicio que espera (o un orquestador, si se usa) trata "ningún evento dentro de la ventana de timeout" como una falla y dispara la misma cadena compensatoria que dispararía un evento de falla explícito | CANCELLED tras la ventana de timeout |
Nota que las compensaciones corren en orden inverso a los pasos hacia adelante que ya tuvieron éxito — esta es la regla general de las sagas: para deshacer el paso N, compensas N, luego N-1, luego N-2, retrocediendo solo por los pasos que realmente se confirmaron.
#Un camino de compensación en detalle
#El flujo completo, camino feliz y compensaciones juntos
#Detalles de diseño que separan una saga funcional de una frágil
Correlación y orden. Cada evento de la saga lleva el ID del pedido, que se usa como clave de partición (Kafka) o ID de grupo de mensajes (SQS FIFO) en cada topic involucrado. Eso garantiza que todos los eventos de un pedido se procesen en orden dentro de cada servicio, sin necesitar ningún orden global entre servicios — que Inventario procese en secuencia los eventos del pedido #4521 es todo lo que importa, independientemente de que el pedido #9987 corra por el mismo pipeline al mismo tiempo en otra partición.
Idempotencia en cada paso. La entrega al menos una vez más los reintentos significa que Pagos podría ver InventoryReserved dos veces para el mismo pedido. Cada consumidor indexa su efecto secundario (cobro, reserva, reembolso) con una clave de idempotencia derivada del ID del pedido y el nombre del paso, así un evento reentregado es un no-op seguro en lugar de un cobro duplicado.
El patrón outbox en cada servicio, no solo en Pedidos. Cada servicio que tanto muta su propio estado como publica el siguiente evento de la cadena tiene el mismo riesgo de doble escritura que tiene Pedidos. Que Inventario reserve stock y publique InventoryReserved necesita la misma disciplina de transacción-local-más-outbox, o reintroduces exactamente la inconsistencia que el patrón saga existe para evitar, solo que un salto más adelante.
Los timeouts son un modo de falla de primera clase. Un paso de la saga que nunca emite su siguiente evento esperado —porque un servicio falló a mitad de proceso, o una dependencia downstream está atascada— necesita un timeout explícito que dispare la misma cadena compensatoria que dispararía un evento de falla. Sin eso, un pedido puede quedarse en PENDING para siempre sin ningún camino automático hacia una resolución.
Dead-letter queues por consumidor, con alertas. Un evento malformado o que falla perpetuamente en la cola de cualquier paso debería ir a dead-letter tras un número acotado de reintentos en lugar de bloquear esa cola indefinidamente — y alguien debe recibir una alerta cuando eso pase, porque un paso de saga atascado es un pedido atascado, y los pedidos atascados son solicitudes de reembolso esperando a ocurrir.
Exponer la consistencia eventual con honestidad. El pedido no está "confirmado" en el instante en que se escribe OrderPlaced — pasa por estados intermedios reales (PENDING, potencialmente REJECTED/CANCELLED) que pueden durar desde milisegundos hasta (durante un incidente) minutos. La interfaz necesita representar eso con honestidad —un spinner o un estado de "procesando"— en lugar de mostrar éxito de forma optimista antes de que la saga realmente haya terminado.
#Si hubieras usado un orquestador en su lugar
Cambiar a orquestación no modifica ninguno de los eventos de arriba — cambia quién decide qué pasa después. Un orquestador de saga consumiría OrderPlaced él mismo, llamaría explícitamente (o emitiría un comando) a Inventario, esperaría InventoryReserved o un timeout, luego avanzaría explícitamente a Pagos, y así sucesivamente — manteniendo el paso actual y el historial de la saga en su propio estado persistido. La cadena compensatoria se convierte en lógica del orquestador ("si falla el pago, llama al endpoint de liberación de Inventario, luego marca el pedido como cancelado") en lugar de algo que tienes que reconstruir leyendo los consumidores de cinco servicios. El costo que asumes: ahora cada servicio necesita exponer los comandos/APIs específicos que el orquestador llama, y el propio orquestador necesita alta disponibilidad, ya que ahora está en el camino crítico de cada pedido.