Orchestration

Declarative processes

Model long-running business flows without giving domain code an infrastructure API.

Read it like the business describes it

OrderFulfillment.cs
C#
public static Process OrderFulfillment => Process
    .StartOn<OrderPlaced>(e => e.OrderId)
    .Then(Stock.Reserve).Retry(5.Times).Undo(Stock.Release)
    .Then(Payments.Charge).Retry(5.Times, Backoff.Exponential).Undo(Payments.Refund)
    .Await<PaymentConfirmed>(e => e.OrderId).Within(15.Minutes)
    .Then(Shipping.Ship).Retry(5.Times)
    .OnCompensated(Orders.Cancel).Retry(5.Times);

What the runtime guarantees

  • Each envelope is handled as one atomic change set.
  • Retries retain stable message identities and follow declared backoff.
  • Completed steps compensate in reverse order after a rejection. Exhausted step retries suspend the instance with a reason instead; .OrCompensate() opts into compensating.
  • A step given up on with an unknown outcome is fenced by its undo: a late copy of it never executes.
  • Early awaited events are parked until the process reaches the matching await.
  • Timeouts use the runtime clock; tests advance it without sleeping.

Retries, undos and suspension

Every step states its own retry policy: .Retry(attempts, backoff) or .NoRetry("why"). There is no default, and analyzer WYRD0014 reports a step that declares neither. Implemented

An undo does not inherit its step's retries. Undos and the outcome command always insist: 10 retries with exponential backoff (waits capped at one minute) by default, adjustable with .Undo(command, 20.Times) and impossible to disable. Implemented

When a step's retries are exhausted, or an undo or the outcome gives up, the instance becomes Suspended with a reason (ReasonOf). An operator calls ResumeAsync or CompensateAsync, and a late success resumes it on its own. .Retry(3.Times).OrCompensate() opts a step into compensating instead. Implemented The rules and the reasons are in How Wyrd behaves and Design decisions.

Observe the exact history

Journal
text
[00:00:00] OrderFulfillment A-1005   started by OrderPlaced
[00:00:00] OrderFulfillment A-1005   -> ReserveStock
[00:00:00] OrderFulfillment A-1005   ok StockReserved
[00:00:00] OrderFulfillment A-1005   -> ChargePayment
[00:00:00] OrderFulfillment A-1005   ok PaymentCharged
[00:00:00] OrderFulfillment A-1005   awaiting PaymentConfirmed (within 15m)
[00:15:00] OrderFulfillment A-1005   timeout: PaymentConfirmed not received in time
[00:15:00] OrderFulfillment A-1005   compensating
[00:15:00] OrderFulfillment A-1005   -> RefundPayment
[00:15:00] OrderFulfillment A-1005   ok PaymentRefunded
[00:15:00] OrderFulfillment A-1005   -> ReleaseStock
[00:15:00] OrderFulfillment A-1005   ok StockReleased
[00:15:00] OrderFulfillment A-1005   compensated