aws.crafter.run

Fiabilidad

Saga por coreografía

También llamado Choreographed saga

Coordinar una operación que toca varios servicios sin director: cada uno reacciona al evento del anterior y publica el suyo, y las compensaciones son también eventos a los que alguien reacciona.

DelicadoVerificado el

El problema

Confirmar un pedido son tres cosas repartidas en tres servicios: reservar el stock, cobrar el pago y avisar al almacén. No hay transacción que abarque las tres, así que hace falta un plan para cuando una falle a mitad.

La saga orquestada resuelve eso con un director: una máquina de estados que conoce los tres pasos, los llama en orden y sabe qué compensar. Funciona muy bien, y tiene un precio que se paga en otra moneda:

En una organización con varios equipos, eso se convierte en una cola de peticiones sobre quien mantiene las máquinas de estados.

La solución

Quitar al director. Cada servicio hace lo suyo, publica el hecho que resulta, y se desentiende. Quien esté interesado, reacciona.

pedidosstockpagosPedidoCreadoStockReservadoPagoFallido · stock lo escucha y libera la reservaLa compensación es otro evento más: nadie ordena deshacer, alguien reaccionaNingún componente sabe en qué punto del proceso está el pedido
Nadie dirige, y nadie tiene la foto completa. Cada servicio conoce solo sus eventos de entrada y de salida — que es lo que hace barato añadir un cuarto — pero el proceso completo no está escrito en ningún sitio, y eso es lo que se paga al depurarlo.

pedidos publica PedidoCreado. stock reacciona, reserva, y publica StockReservado. pagos reacciona e intenta cobrar; si falla, publica PagoFallido. Y stock, que también escucha ese evento, libera la reserva.

Lo importante es lo que no hay:

Por eso añadir un cuarto participante es gratis para los otros tres: el antifraude se suscribe a PedidoCreado y ya está. Nadie modifica nada, nadie despliega nada, nadie se entera.

Cuándo usarlo

Cuándo NO usarlo

Cómo implementarlo

  1. Nombra los eventos como hechos pasados y trátalos como contrato público: PedidoCreado, StockReservado, PagoFallido. Con versión en el payload.
  2. Un identificador de correlación en todos, el mismo de punta a punta. Es lo único que después permitirá reconstruir el proceso.
  3. Cada servicio publica con outbox, en la misma transacción que su cambio de estado. Sin eso, un eslabón que falla al publicar corta la cadena y nadie se entera.
  4. Cola SQS entre el bus y cada consumidor, con su DLQ.
  5. Consumidores idempotentes. Toda la cadena es “al menos una vez”.
  6. Define los eventos de compensación con el mismo cuidado que los de éxito: PagoFallido es tan parte del contrato como PagoCompletado.
  7. Dibuja la cadena en algún sitio, aunque el código no la contenga. Un diagrama en el repositorio es lo único que va a evitar que nadie entienda el proceso dentro de seis meses.

El código

// ── stock: reacciona a un hecho y publica el suyo ───────────────────
export const reservarStock = async (evento: EventBridgeEvent<'PedidoCreado', Pedido>) => {
  const pedido = evento.detail;

  // Idempotencia: la entrega es al menos una vez, siempre.
  if (await yaReservado(pedido.id)) return;

  // Cambiar el estado y encolar el hecho en la MISMA transaccion.
  // Es el patron outbox: si no se publica el evento, tampoco se reserva.
  await ddb.send(new TransactWriteCommand({
    TransactItems: [
      { Put: { TableName: 'stock', Item: reserva(pedido) } },
      { Put: { TableName: 'stock', Item: outbox({
          tipo: 'StockReservado',
          correlacion: pedido.correlacion,   // el hilo de toda la cadena
          detalle: { pedidoId: pedido.id, unidades: pedido.unidades },
        }) } },
    ],
  }));
};
// ── stock, otra vez: la compensacion es solo otra suscripcion ───────
// Nadie le ordena deshacer. Escucha PagoFallido igual que escuchaba
// PedidoCreado, y actua.
export const liberarStock = async (evento: EventBridgeEvent<'PagoFallido', Fallo>) => {
  const { pedidoId } = evento.detail;
  if (!(await hayReserva(pedidoId))) return;   // idempotente
  await liberar(pedidoId);
};

Te va a morder

Coste

Concepto Efecto
Publicaciones en el bus ~1,00 USD / millón en EventBridge, ~0,50 en SNS
Colas y consumidores uno por participante y por evento escuchado
Transiciones de estado ninguna — no hay máquina de estados
Tiempo de depuración sube, y no aparece en la factura

Frente a la orquestación, la coreografía elimina el coste de las transiciones de estado, que en Step Functions Standard es la línea dominante: una saga de seis pasos a un millón de ejecuciones son seis millones de transiciones. Aquí son publicaciones y consumos, que salen bastante más baratos.

Lo que sube es el coste humano, y sube en el peor momento: durante un incidente, cuando hay que reconstruir a mano por dónde iba un proceso que no está escrito en ningún sitio. Ese coste no está en ninguna calculadora y es el que verdaderamente decide entre los dos patrones.

Fuentes

Ausencia de garantías de orden, destinos por regla y throughput por región en EventBridge: Amazon EventBridge quotas. Entrega al menos una vez de SNS y SQS, que obliga a la idempotencia de todos los eslabones: Amazon SQS standard queues y Amazon SNS message delivery retries. Semántica exactamente-una-vez de Step Functions Standard, usada en la comparación con la orquestación: Choosing workflow type in Step Functions. TransactWriteItems como base del outbox de cada eslabón: Amazon DynamoDB Transactions.

Patrones relacionados

Un enlace sin la relación nombrada es un "ver también". Aquí cada uno dice qué relación tiene y por qué.