Orchestrations

Coordinate operations across multiple Bounded Contexts

Coordinates operations across multiple Bounded Contexts.


When to Use

ScenarioApproach
Single BC, self-containedDirect BC call
Multiple BCs neededOrchestration

Three Types

TypePurposeExample
CompositionRead-only, multi-BC queryGET order with customer + products
WorkflowWrite, multi-BC commandCheckout: order + inventory + payment
ProjectionDenormalized read storeDashboard aggregating multiple BCs

Structure

Each orchestration is self-contained with its own layers:

orchestrations/{orchestration-name}/
├── app/
│   ├── inbound/
│   │   ├── compositions/
│   │   │   └── {composition}/
│   │   ├── workflows/
│   │   │   └── {workflow}/
│   │   └── projections/
│   │       └── {projection}/
│   └── outbound/
├── infra/
│   ├── outbound-adapters/
│   ├── persistence/
│   └── schemas/
└── presentation/
    ├── bootstrap/
    └── http/

Note: Each orchestration has its own presentation layer for HTTP endpoints specific to that orchestration.


Composition

compositions/use-cases/get-order-details.composition.ts
class GetOrderDetailsComposition implements GetOrderDetailsCompositionInboundPort {
  constructor(
    orderQueryRepo: OrderQueryRepositoryOutboundPort,
    customerQueryRepo: CustomerQueryRepositoryOutboundPort,
  ) {
    this.findOrderQuery = new FindOrderByIdQuery(orderQueryRepo);
    this.findCustomerQuery = new FindCustomerByIdQuery(customerQueryRepo);
  }

  async execute(input: GetOrderDetailsInput): Promise<GetOrderDetailsOutput> {
    const order = await this.findOrderQuery.execute({ orderId: input.orderId });
    const customer = await this.findCustomerQuery.execute({ customerId: order.customerId });
    return { order, customer };
  }
}

Workflow

workflows/use-cases/process-checkout.workflow.ts
class ProcessCheckoutWorkflow implements ProcessCheckoutWorkflowInboundPort {
  async execute(input: ProcessCheckoutInput): Promise<ProcessCheckoutOutput> {
    const order = await this.createOrderCommand.execute({ ... });
    
    try {
      await this.reserveInventoryCommand.execute({ orderId: order.id });
      await this.processPaymentCommand.execute({ orderId: order.id });
      return { orderId: order.id, status: 'COMPLETED' };
    } catch (error) {
      await this.cancelOrderCommand.execute({ orderId: order.id });
      throw error;
    }
  }
}

Projection

Projections have their own infrastructure layer for denormalized storage.

Rules:

  • CAN read from shared database
  • CAN write only to its own denormalized storage
  • For simple systems with shared DB, may not need separate storage
projections/use-cases/user-dashboard.projection.ts
class UserDashboardProjection implements UserDashboardProjectionInboundPort {
  async execute(input: UserDashboardInput): Promise<UserDashboardOutput> {
    // Reads from multiple BCs, returns aggregated view
  }
}

Naming

TypeInterfaceFile
Composition{Name}CompositionInboundPort.composition.ts
Workflow{Name}WorkflowInboundPort.workflow.ts
Projection{Name}ProjectionInboundPort.projection.ts