← Volver al listado de tecnologías

Capítulo 3: Sagas - Orquestación vs Coreografía

Por: SiempreListo
sagaorquestacióncoreografíaarquitectura

Capítulo 3: Sagas - Orquestación vs Coreografía

“Dos maneras de coordinar una danza distribuida”

Introducción

Cuando implementamos el patrón Saga, tenemos dos enfoques fundamentales para coordinar los pasos de la transacción distribuida: Coreografía y Orquestación. Ambos logran el mismo objetivo (ejecutar una secuencia de transacciones locales con sus compensaciones), pero difieren en cómo se toman las decisiones y quién controla el flujo.

La diferencia es similar a la música: en una coreografía, cada bailarín sabe qué hacer cuando escucha cierta música (reacciona a eventos). En una orquesta, hay un director que indica a cada músico cuándo y qué tocar (control centralizado).

Coreografía

En el enfoque de coreografía, no hay un controlador central. Cada servicio sabe qué hacer cuando recibe ciertos eventos y qué eventos publicar cuando termina su trabajo. Los servicios están “coreografiados” para trabajar juntos sin que nadie les dé instrucciones directas.

Un evento en este contexto es un mensaje que notifica que algo ocurrió. Por ejemplo, “OrderCreated” (pedido creado) o “PaymentProcessed” (pago procesado). Los eventos son hechos inmutables del pasado.

Cada servicio reacciona a eventos y publica sus propios eventos:

sequenceDiagram
    participant O as Order
    participant I as Inventory
    participant P as Payment
    participant S as Shipping

    O->>O: OrderCreated
    O-->>I: OrderCreated event

    I->>I: StockReserved
    I-->>P: StockReserved event

    P->>P: PaymentProcessed
    P-->>S: PaymentProcessed event

    S->>S: ShipmentScheduled
    S-->>O: ShipmentScheduled event

    O->>O: OrderCompleted

Implementación Coreografía

// Order Service - Publica evento
class OrderService {
  async createOrder(data: CreateOrderData): Promise<Order> {
    const order = await this.orderRepository.save({
      ...data,
      status: 'pending'
    });

    await this.eventBus.publish({
      type: 'OrderCreated',
      orderId: order.id,
      customerId: data.customerId,
      items: data.items,
      total: data.total
    });

    return order;
  }
}

// Inventory Service - Reacciona a evento
class InventoryEventHandler {
  @OnEvent('OrderCreated')
  async handleOrderCreated(event: OrderCreatedEvent): Promise<void> {
    try {
      await this.inventoryService.reserveStock(event.orderId, event.items);

      await this.eventBus.publish({
        type: 'StockReserved',
        orderId: event.orderId,
        items: event.items
      });
    } catch (error) {
      await this.eventBus.publish({
        type: 'StockReservationFailed',
        orderId: event.orderId,
        reason: error.message
      });
    }
  }
}

// Payment Service - Reacciona a StockReserved
class PaymentEventHandler {
  @OnEvent('StockReserved')
  async handleStockReserved(event: StockReservedEvent): Promise<void> {
    try {
      const payment = await this.paymentService.processPayment(event.orderId);

      await this.eventBus.publish({
        type: 'PaymentProcessed',
        orderId: event.orderId,
        paymentId: payment.id
      });
    } catch (error) {
      await this.eventBus.publish({
        type: 'PaymentFailed',
        orderId: event.orderId,
        reason: error.message
      });
    }
  }
}

// Order Service - Maneja compensación
class OrderEventHandler {
  @OnEvent('PaymentFailed')
  async handlePaymentFailed(event: PaymentFailedEvent): Promise<void> {
    // La compensación se propaga
    await this.eventBus.publish({
      type: 'OrderCancelled',
      orderId: event.orderId,
      reason: event.reason
    });
  }
}

// Inventory Service - Compensa stock
class InventoryCompensationHandler {
  @OnEvent('OrderCancelled')
  async handleOrderCancelled(event: OrderCancelledEvent): Promise<void> {
    await this.inventoryService.releaseStock(event.orderId);

    await this.eventBus.publish({
      type: 'StockReleased',
      orderId: event.orderId
    });
  }
}

Orquestación

En el enfoque de orquestación, existe un componente central llamado orquestador (u orchestrator) que conoce todos los pasos de la saga y se encarga de invocar a cada servicio en el orden correcto. Es como un director de orquesta que indica a cada músico cuándo tocar.

El orquestador mantiene el estado de la saga y sabe exactamente en qué paso está cada transacción. Si algo falla, el orquestador es quien decide qué compensaciones ejecutar y en qué orden.

Un orquestador central coordina todos los pasos:

sequenceDiagram
    participant Orch as Orchestrator
    participant O as Order
    participant I as Inventory
    participant P as Payment
    participant S as Shipping

    Orch->>O: Create Order
    O-->>Orch: Order Created

    Orch->>I: Reserve Stock
    I-->>Orch: Stock Reserved

    Orch->>P: Process Payment
    P-->>Orch: Payment Processed

    Orch->>S: Schedule Shipment
    S-->>Orch: Shipment Scheduled

    Orch->>O: Complete Order

Implementación Orquestación

// Definición de la Saga
interface SagaStep<T> {
  name: string;
  execute: (context: T) => Promise<void>;
  compensate: (context: T) => Promise<void>;
}

class CreateOrderSaga {
  private steps: SagaStep<OrderSagaContext>[] = [
    {
      name: 'createOrder',
      execute: async (ctx) => {
        ctx.order = await this.orderService.create(ctx.input);
      },
      compensate: async (ctx) => {
        await this.orderService.cancel(ctx.order!.id);
      }
    },
    {
      name: 'reserveStock',
      execute: async (ctx) => {
        ctx.reservation = await this.inventoryService.reserve(
          ctx.order!.id,
          ctx.input.items
        );
      },
      compensate: async (ctx) => {
        await this.inventoryService.release(ctx.reservation!.id);
      }
    },
    {
      name: 'processPayment',
      execute: async (ctx) => {
        ctx.payment = await this.paymentService.process(
          ctx.order!.id,
          ctx.input.total
        );
      },
      compensate: async (ctx) => {
        await this.paymentService.refund(ctx.payment!.id);
      }
    },
    {
      name: 'scheduleShipment',
      execute: async (ctx) => {
        ctx.shipment = await this.shippingService.schedule(
          ctx.order!.id,
          ctx.input.shippingAddress
        );
      },
      compensate: async (ctx) => {
        await this.shippingService.cancel(ctx.shipment!.id);
      }
    }
  ];

  async execute(input: CreateOrderInput): Promise<Order> {
    const context: OrderSagaContext = { input };
    const completedSteps: SagaStep<OrderSagaContext>[] = [];

    try {
      for (const step of this.steps) {
        console.log(`Executing step: ${step.name}`);
        await step.execute(context);
        completedSteps.push(step);
      }

      // Marcar orden como completada
      await this.orderService.complete(context.order!.id);
      return context.order!;

    } catch (error) {
      console.error(`Saga failed at step, compensating...`);

      // Compensar en orden inverso
      for (const step of completedSteps.reverse()) {
        try {
          console.log(`Compensating step: ${step.name}`);
          await step.compensate(context);
        } catch (compError) {
          console.error(`Compensation failed for ${step.name}`, compError);
          // Registrar para retry manual
        }
      }

      throw error;
    }
  }
}

interface OrderSagaContext {
  input: CreateOrderInput;
  order?: Order;
  reservation?: StockReservation;
  payment?: Payment;
  shipment?: Shipment;
}

Comparación

El acoplamiento se refiere a qué tan dependientes son los servicios entre sí. Acoplamiento bajo significa que los servicios pueden cambiar sin afectar a otros.

La visibilidad del flujo indica qué tan fácil es entender la secuencia completa de operaciones mirando el código.

AspectoCoreografíaOrquestación
AcoplamientoBajoMedio
ComplejidadDistribuidaCentralizada
DebuggingDifícilFácil
Single Point of FailureNoSí (orquestador)
EscalabilidadAltaMedia
Visibilidad del flujoBajaAlta

Cuándo Usar Cada Enfoque

Coreografía

✅ Ideal cuando:

❌ Evitar cuando:

Orquestación

✅ Ideal cuando:

❌ Evitar cuando:

Enfoque Híbrido

En la práctica, muchos sistemas combinan ambos enfoques. Puedes usar orquestación para los pasos críticos del flujo principal (donde necesitas control y visibilidad) y coreografía para operaciones secundarias (donde prefieres desacoplamiento).

graph TD
    subgraph "Saga Orquestada"
        O[Orchestrator]
        OS[Order Service]
        IS[Inventory Service]
        PS[Payment Service]
    end

    subgraph "Coreografía"
        PS --> NS[Notification Service]
        PS --> AS[Analytics Service]
    end

Resumen

Glosario

Orquestación (de Sagas)

Definición: Enfoque de coordinación donde un componente central (el orquestador) controla el flujo de la saga, invocando explícitamente a cada servicio participante en el orden correcto.

Por qué es importante: Centraliza la lógica del flujo de negocio en un solo lugar, facilitando entender, debuggear y modificar la secuencia de pasos. El orquestador mantiene el estado completo de la saga.

Ejemplo práctico: El orquestador de pedidos sabe que primero debe llamar a Order Service, luego a Inventory Service, después a Payment Service. Si Payment falla, el orquestador llama a las compensaciones en orden inverso.


Coreografía (de Sagas)

Definición: Enfoque de coordinación donde no hay un controlador central. Cada servicio sabe qué eventos escuchar y qué eventos publicar, formando una cadena de reacciones.

Por qué es importante: Maximiza el desacoplamiento entre servicios. Cada equipo puede trabajar de forma independiente. No hay un punto único de fallo en la coordinación.

Ejemplo práctico: Order Service publica “OrderCreated”. Inventory Service escucha ese evento, reserva stock, y publica “StockReserved”. Payment Service escucha “StockReserved”, procesa el pago, y publica “PaymentProcessed”.


Orquestador

Definición: Componente de software que coordina la ejecución de una saga orquestada. Conoce todos los pasos, mantiene el estado, invoca servicios y gestiona compensaciones.

Por qué es importante: Es el “cerebro” de la saga orquestada. Toda la lógica de coordinación está en un lugar, lo que facilita entender el flujo completo y agregar monitoreo.

Ejemplo práctico: El SagaOrchestrator recibe una solicitud de crear pedido, ejecuta secuencialmente cada paso, guarda el estado en cada transición, y si algo falla, coordina el rollback.


Evento (de Dominio)

Definición: Mensaje que notifica que algo significativo ocurrió en el dominio del negocio. Es un hecho inmutable del pasado, no una solicitud de acción.

Por qué es importante: Los eventos permiten comunicación asíncrona y desacoplada entre servicios. Un servicio puede publicar eventos sin saber quién los consumirá.

Ejemplo práctico: “OrderCreated” con datos {orderId: “123”, customerId: “456”, items: […]}. El evento describe un hecho que ya pasó: se creó un pedido.


Event Bus (Bus de Eventos)

Definición: Infraestructura de mensajería que permite a los servicios publicar eventos y suscribirse a eventos de otros servicios. Actúa como intermediario.

Por qué es importante: Desacopla a los productores de eventos de los consumidores. Los servicios no necesitan conocerse entre sí; solo necesitan conocer el bus de eventos.

Ejemplo práctico: Order Service publica “OrderCreated” en el bus. Inventory Service y Notification Service están suscritos a ese tipo de evento y lo reciben automáticamente.


Acoplamiento

Definición: Grado de interdependencia entre componentes de software. Alto acoplamiento significa que un cambio en un componente probablemente requiera cambios en otros.

Por qué es importante: El bajo acoplamiento permite que los equipos trabajen de forma independiente, facilita el testing y hace el sistema más flexible ante cambios.

Ejemplo práctico: En coreografía, Order Service no conoce a Inventory Service directamente (bajo acoplamiento). En orquestación, el orquestador conoce a todos los servicios (acoplamiento medio).


Event Handler (Manejador de Eventos)

Definición: Función o componente que se ejecuta automáticamente cuando se recibe un evento específico. “Maneja” o “procesa” el evento.

Por qué es importante: Es el mecanismo que permite a los servicios reaccionar a eventos en el patrón de coreografía. Cada handler encapsula la lógica de respuesta a un tipo de evento.

Ejemplo práctico: InventoryEventHandler.handleOrderCreated() se ejecuta cada vez que llega un evento “OrderCreated”. Dentro, reserva el stock y publica “StockReserved” o “StockReservationFailed”.


Decorator/Annotation

Definición: Característica de lenguajes de programación que permite agregar metadatos o comportamiento a clases, métodos o propiedades de forma declarativa.

Por qué es importante: Simplifica el código al separar aspectos técnicos (como suscripción a eventos) de la lógica de negocio. Hace el código más legible.

Ejemplo práctico: @OnEvent('OrderCreated') sobre un método indica que ese método debe ejecutarse cuando llegue un evento de tipo “OrderCreated”, sin necesidad de código explícito de suscripción.


Flujo de Control

Definición: La secuencia en que se ejecutan las instrucciones o pasos de un programa o proceso de negocio.

Por qué es importante: Entender el flujo de control es esencial para debuggear problemas. En orquestación es explícito; en coreografía está distribuido y puede ser difícil de seguir.

Ejemplo práctico: En orquestación, el flujo es: crearPedido() -> reservarStock() -> procesarPago() -> completarPedido(). En coreografía, el flujo emerge de las reacciones a eventos.


Visibilidad del Estado

Definición: Capacidad de observar y entender el estado actual de una transacción o proceso en un momento dado.

Por qué es importante: Sin visibilidad, es difícil diagnosticar problemas, responder preguntas de soporte o entender por qué algo falló. La orquestación ofrece mejor visibilidad porque el estado está centralizado.

Ejemplo práctico: Con orquestación, puedes consultar “¿en qué paso está el pedido 123?” y obtener “esperando confirmación de pago”. Con coreografía, necesitarías consultar múltiples servicios.


← Capítulo 2: Two-Phase Commit | Capítulo 4: Transacciones Compensatorias →