11.3 Sagas
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, wherexis 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
kstarts the compensations of the completed stepsk−1 … 1in 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 resultx, 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 fromundo. A possible extension — after 1.0: compensation of steps with an unknown outcome keyed by the idempotency key (the formundo? key => …). - A compensation is retried with backoff up to
SAGA_UNDO_RETRIEStimes. If it never succeeds, the saga gets the statusstuck, returnsCompensationFailed{ 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, whereEare the step errors.
Related diagnostics
See also
Source: docs/spec/oxl-standard.en.md