Order Processing

A multi-step order processing workflow with payment, fulfillment, and compensation (rollback). Demonstrates the saga pattern for distributed transactions.

Prerequisites: Workflows, Compensation & Sagas, Activities. What you'll learn: How to orchestrate multi-step business processes with automatic rollback.

The Pattern

  1. Validate the order
  2. Reserve inventory
  3. Charge payment
  4. Fulfill the order
  5. If any step fails, compensate (rollback) completed steps in reverse order

Workflow

workflow process_order v1 {
  node validate = validate_order v1
  node reserve = reserve_inventory v1
  node charge = charge_payment v1
  node fulfill = fulfill_order v1
  node notify = send_confirmation v1

  edge validate -> reserve
  edge reserve -> charge
  edge charge -> fulfill
  edge fulfill -> notify

  on compensated -> OrderResult {
    return {
      order_id: input.order_id,
      status: "cancelled",
      reason: compensation.reason
    }
  }
}

Activities with Compensation

Each activity defines what happens if it needs to be rolled back:

activity reserve_inventory v1 {
  data reservation {
    fetch warehouse.reserve {
      items: input.items
    }
  }

  apply(input: OrderInput) -> ReservationResult {
    return {
      reservation_id: reservation.id,
      items: reservation.items
    }
  }

  compensate {
    fetch warehouse.release {
      reservation_id: input.reservation_id
    }
  }

  retry_policy {
    max_attempts: 3
    backoff: 1s
  }
}

activity charge_payment v1 {
  data charge {
    fetch stripe.create_charge {
      amount: input.total
      currency: input.currency
      customer_id: input.customer_id
    }
  }

  apply(input: PaymentInput) -> ChargeResult {
    return {
      charge_id: charge.id,
      status: charge.status
    }
  }

  compensate {
    fetch stripe.refund {
      charge_id: input.charge_id
    }
  }

  retry_policy {
    max_attempts: 3
    backoff: 2s
    max_backoff: 30s
  }
}

How Compensation Works

If fulfill_order fails after charge_payment and reserve_inventory have succeeded:

  1. The workflow engine detects the failure
  2. It calls charge_payment.compensate (refund the charge)
  3. It calls reserve_inventory.compensate (release the reservation)
  4. Compensation runs in LIFO order (last completed, first compensated)
  5. The workflow's on compensated block returns the final result

See Also