Chuyển đến nội dung chính

Lesson 12: Order Management System — State Machine, Saga Pattern & Order Orchestration

OMS architecture for POD, order state machine, Saga pattern for distributed transactions, split orders (multi-supplier), order orchestration, compensation logic, order event sourcing, idempotency.

🏗️ Architecture — Lesson 12 Lesson 12: Order Management System — State Machine, Saga Pattern & Order Orchestration

Fashion Design & Print-on-Demand System Architecture — From Domain Analysis to Production

Part 4: Order Processing & Fulfillment

xdev.asia

1. POD Order Lifecycle

POD Order Flow (không có inventory — produce on demand)

Customer                    POD Platform                   Supplier/Printer
   │                            │                               │
   │  Place Order               │                               │
   ├───────────────────────────▶│                               │
   │                            │  Validate + Payment           │
   │                            │──────────┐                    │
   │                            │          │                    │
   │                            │◀─────────┘                    │
   │                            │                               │
   │                            │  Prepare print files          │
   │                            │──────────┐                    │
   │                            │          │ (render hi-res,    │
   │                            │◀─────────┘  color convert)    │
   │                            │                               │
   │                            │  Submit to Supplier           │
   │                            ├──────────────────────────────▶│
   │                            │                               │
   │                            │         Printing...           │
   │                            │           │                   │
   │                            │  Status: In Production        │
   │                            │◀──────────────────────────────┤
   │  "Your order is being      │                               │
   │   printed!"                │         Quality Check          │
   │◀───────────────────────────┤           │                   │
   │                            │  Status: Shipped + Tracking   │
   │                            │◀──────────────────────────────┤
   │  Tracking notification     │                               │
   │◀───────────────────────────┤                               │
   │                            │                               │
   │  Delivered                 │                               │
   │◀───────────────────────────┤                               │

2. Order State Machine

// Order states specific to POD workflow
enum OrderStatus {
  // Checkout phase
  PENDING_PAYMENT = 'pending_payment',
  PAYMENT_FAILED = 'payment_failed',
  PAID = 'paid',
  
  // Processing phase
  PROCESSING = 'processing',           // Preparing print files
  FILE_READY = 'file_ready',           // Print-ready files generated
  
  // Production phase (at supplier)
  SUBMITTED_TO_SUPPLIER = 'submitted_to_supplier',
  IN_PRODUCTION = 'in_production',     // Printing/pressing
  QUALITY_CHECK = 'quality_check',     // QC at supplier
  PRODUCTION_FAILED = 'production_failed',  // Print defect
  
  // Shipping phase
  PACKED = 'packed',
  SHIPPED = 'shipped',
  IN_TRANSIT = 'in_transit',
  OUT_FOR_DELIVERY = 'out_for_delivery',
  DELIVERED = 'delivered',
  
  // Exception states
  CANCELLED = 'cancelled',
  REFUNDED = 'refunded',
  RETURN_REQUESTED = 'return_requested',
  RETURNED = 'returned',
}

// Valid state transitions
const ORDER_TRANSITIONS: Record<OrderStatus, OrderStatus[]> = {
  [OrderStatus.PENDING_PAYMENT]: [OrderStatus.PAID, OrderStatus.PAYMENT_FAILED, OrderStatus.CANCELLED],
  [OrderStatus.PAYMENT_FAILED]: [OrderStatus.PENDING_PAYMENT, OrderStatus.CANCELLED],
  [OrderStatus.PAID]: [OrderStatus.PROCESSING, OrderStatus.CANCELLED],
  [OrderStatus.PROCESSING]: [OrderStatus.FILE_READY, OrderStatus.CANCELLED],
  [OrderStatus.FILE_READY]: [OrderStatus.SUBMITTED_TO_SUPPLIER],
  [OrderStatus.SUBMITTED_TO_SUPPLIER]: [OrderStatus.IN_PRODUCTION, OrderStatus.PRODUCTION_FAILED],
  [OrderStatus.IN_PRODUCTION]: [OrderStatus.QUALITY_CHECK, OrderStatus.PRODUCTION_FAILED],
  [OrderStatus.QUALITY_CHECK]: [OrderStatus.PACKED, OrderStatus.PRODUCTION_FAILED],
  [OrderStatus.PRODUCTION_FAILED]: [OrderStatus.SUBMITTED_TO_SUPPLIER, OrderStatus.REFUNDED],  // Retry or refund
  [OrderStatus.PACKED]: [OrderStatus.SHIPPED],
  [OrderStatus.SHIPPED]: [OrderStatus.IN_TRANSIT],
  [OrderStatus.IN_TRANSIT]: [OrderStatus.OUT_FOR_DELIVERY, OrderStatus.DELIVERED],
  [OrderStatus.OUT_FOR_DELIVERY]: [OrderStatus.DELIVERED],
  [OrderStatus.DELIVERED]: [OrderStatus.RETURN_REQUESTED],
  [OrderStatus.RETURN_REQUESTED]: [OrderStatus.RETURNED, OrderStatus.DELIVERED],
  [OrderStatus.RETURNED]: [OrderStatus.REFUNDED],
  [OrderStatus.CANCELLED]: [OrderStatus.REFUNDED],
  [OrderStatus.REFUNDED]: [],
};

class OrderStateMachine {
  transition(order: Order, newStatus: OrderStatus): Order {
    const allowedTransitions = ORDER_TRANSITIONS[order.status];
    
    if (!allowedTransitions.includes(newStatus)) {
      throw new InvalidStateTransitionError(
        `Cannot transition from ${order.status} to ${newStatus}`
      );
    }

    return {
      ...order,
      status: newStatus,
      statusHistory: [
        ...order.statusHistory,
        { status: newStatus, timestamp: new Date(), actor: 'system' },
      ],
    };
  }
}

3. Saga Pattern — Order Orchestration

// Orchestration Saga cho order processing
// Mỗi step có compensation (rollback) nếu step sau fail

interface OrderSaga {
  steps: SagaStep[];
  execute(order: Order): Promise<SagaResult>;
}

interface SagaStep {
  name: string;
  execute: (context: SagaContext) => Promise<void>;
  compensate: (context: SagaContext) => Promise<void>;  // Rollback
}

const orderSagaSteps: SagaStep[] = [
  {
    name: 'validate_order',
    execute: async (ctx) => {
      await validateOrderItems(ctx.order);
      await checkSupplierAvailability(ctx.order);
    },
    compensate: async (ctx) => {
      // Nothing to compensate
    },
  },
  {
    name: 'process_payment',
    execute: async (ctx) => {
      ctx.paymentResult = await paymentService.charge(ctx.order);
    },
    compensate: async (ctx) => {
      // Refund payment
      await paymentService.refund(ctx.paymentResult.chargeId, ctx.order.total);
    },
  },
  {
    name: 'generate_print_files',
    execute: async (ctx) => {
      ctx.printFiles = await printFileService.generate(ctx.order);
    },
    compensate: async (ctx) => {
      await printFileService.cleanup(ctx.printFiles);
    },
  },
  {
    name: 'submit_to_supplier',
    execute: async (ctx) => {
      ctx.supplierOrder = await supplierService.submitOrder(
        ctx.order,
        ctx.printFiles,
      );
    },
    compensate: async (ctx) => {
      await supplierService.cancelOrder(ctx.supplierOrder.id);
    },
  },
  {
    name: 'notify_customer',
    execute: async (ctx) => {
      await notificationService.sendOrderConfirmation(ctx.order);
    },
    compensate: async (ctx) => {
      await notificationService.sendOrderCancellation(ctx.order);
    },
  },
];

// Saga executor with automatic compensation on failure
class SagaExecutor {
  async execute(steps: SagaStep[], context: SagaContext): Promise<SagaResult> {
    const completedSteps: SagaStep[] = [];

    for (const step of steps) {
      try {
        await step.execute(context);
        completedSteps.push(step);
      } catch (error) {
        // Compensate all completed steps in reverse order
        for (const completedStep of completedSteps.reverse()) {
          try {
            await completedStep.compensate(context);
          } catch (compensateError) {
            // Log compensation failure — needs manual intervention
            await alertService.criticalAlert({
              type: 'saga_compensation_failed',
              step: completedStep.name,
              orderId: context.order.id,
              error: compensateError.message,
            });
          }
        }

        return { success: false, failedStep: step.name, error: error.message };
      }
    }

    return { success: true };
  }
}

4. Split Orders (Multi-supplier)

// Khi 1 order có items từ nhiều suppliers → split thành sub-orders
interface OrderSplitter {
  split(order: Order): SubOrder[];
}

interface SubOrder {
  id: string;
  parentOrderId: string;
  supplierId: string;
  items: OrderItem[];
  status: OrderStatus;
  
  // Supplier-specific
  supplierOrderId?: string;    // ID từ supplier API
  shippingMethod: string;
  trackingNumber?: string;
}

function splitOrderBySupplier(order: Order): SubOrder[] {
  // Group items by optimal supplier
  const supplierGroups = new Map<string, OrderItem[]>();

  for (const item of order.items) {
    const bestSupplier = selectBestSupplier(item, order.shippingAddress);
    const group = supplierGroups.get(bestSupplier.id) || [];
    group.push(item);
    supplierGroups.set(bestSupplier.id, group);
  }

  return Array.from(supplierGroups.entries()).map(([supplierId, items]) => ({
    id: generateSubOrderId(),
    parentOrderId: order.id,
    supplierId,
    items,
    status: OrderStatus.PROCESSING,
    shippingMethod: selectShippingMethod(supplierId, order.shippingAddress),
  }));
}

// Parent order status = worst child status
function aggregateOrderStatus(subOrders: SubOrder[]): OrderStatus {
  const priorities = [
    OrderStatus.PRODUCTION_FAILED,    // Highest priority (worst)
    OrderStatus.PROCESSING,
    OrderStatus.FILE_READY,
    OrderStatus.SUBMITTED_TO_SUPPLIER,
    OrderStatus.IN_PRODUCTION,
    OrderStatus.QUALITY_CHECK,
    OrderStatus.PACKED,
    OrderStatus.SHIPPED,
    OrderStatus.IN_TRANSIT,
    OrderStatus.DELIVERED,            // Lowest priority (best)
  ];

  let worstIndex = priorities.length - 1;
  for (const sub of subOrders) {
    const idx = priorities.indexOf(sub.status);
    if (idx >= 0 && idx < worstIndex) {
      worstIndex = idx;
    }
  }

  return priorities[worstIndex];
}

5. Order Event Sourcing

// Mọi thay đổi order được lưu dưới dạng events
interface OrderEvent {
  eventId: string;
  orderId: string;
  eventType: string;
  data: Record<string, unknown>;
  timestamp: Date;
  actor: string;                     // 'system', 'customer', 'admin', 'supplier'
  version: number;                   // For optimistic concurrency
}

type OrderEventType =
  | 'OrderCreated'
  | 'PaymentReceived'
  | 'PrintFileGenerated'
  | 'SubmittedToSupplier'
  | 'ProductionStarted'
  | 'QualityCheckPassed'
  | 'QualityCheckFailed'
  | 'Shipped'
  | 'TrackingUpdated'
  | 'Delivered'
  | 'CancelRequested'
  | 'Cancelled'
  | 'RefundIssued'
  | 'ReturnRequested';

// Rebuild order state from events
function rehydrateOrder(events: OrderEvent[]): Order {
  let order: Partial = {};

  for (const event of events) {
    switch (event.eventType) {
      case 'OrderCreated':
        order = { ...event.data, status: OrderStatus.PENDING_PAYMENT };
        break;
      case 'PaymentReceived':
        order.status = OrderStatus.PAID;
        order.paymentId = event.data.paymentId as string;
        break;
      case 'Shipped':
        order.status = OrderStatus.SHIPPED;
        order.trackingNumber = event.data.trackingNumber as string;
        break;
      // ... handle each event type
    }
  }

  return order as Order;
}

6. Summary

PatternPurposePOD Context
State MachineEnforce valid order transitions15+ states specific to POD production flow
Saga PatternDistributed transaction coordinationPayment → PrintFile → Supplier → Notify with rollback
Split OrdersMulti-supplier fulfillment1 customer order → N supplier sub-orders
Event SourcingComplete audit trailReconstruct order state from event history
IdempotencyHandle duplicate webhooks/retriesIdempotency key per operation