OxL Reference

11.3 Sagas

oxl
saga checkout(cart: Cart) -> Order {
  Inventory.reserve(cart)          undo r => Inventory.release(r.id)
  -> Payments.charge(cart.total)   undo p => Payments.refund(p.id)
  -> Orders.create(cart, it)
}
  • A saga is a sequence of steps (the step syntax is as in a chain, §8.4); after a step there MAY be undo x => compensation, where x is the result of the step.
  • A saga is executed on a node with a role that has runs sagas. A call from another node is redirected there.
  • Before and after each step the state of the saga is written to the journal of the built-in storage. After a node failure another node of the role continues the saga from the last recorded step.
  • An error in step k starts the compensations of the completed steps k−1 … 1 in reverse order; then the saga returns the original error.
  • A step whose outcome is unknown when the saga is compensated (the caller's deadline has expired; the executor lost its lease during a node restart and another node took the saga over) counts as failed and is NOT compensated: its undo x => … needs the result x, which does not exist. A late effect of such a step (for example a charge) can appear after the compensations of the earlier steps and stay for good. Therefore the called service MUST be able to cancel or neutralize a late effect of a step by the idempotency key of the step (ctx.idempotencyKey): remember the cancellation (a "tombstone") for that key, so that a late call with it has no effect. The cancellation is usually done by the compensation of the previous step (it may also cancel the later steps of the saga) or by a dedicated cancel rpc called from undo. A possible extension — after 1.0: compensation of steps with an unknown outcome keyed by the idempotency key (the form undo? key => …).
  • A compensation is retried with backoff up to SAGA_UNDO_RETRIES times. If it never succeeds, the saga gets the status stuck, returns CompensationFailed{ cause, step } and is visible in the web interface (part III) for manual resolution.
  • Each step receives an idempotency key ctx.idempotencyKey = sagaId + step number; therefore calls in steps and compensations MUST be @idempotent (otherwise a compile error).
  • The type of a saga call is R | E | CompensationFailed | NetError, where E are the step errors.

saga-decl, saga-idempotency

See also

Source: docs/spec/oxl-standard.en.md