Summary#
Workflow.Run(order) starts the workflow whose target binds the entity's type, on that entity — the same
inference Raising a workflow event uses for its events. There are two forms, and the only difference is await:
Workflow.Run(order)(bare) — fire-and-forget: start it and carry straight on. The call returns nothing.await Workflow.Run("fulfil", order)— a durable wait: hold here until the started workflow reaches a terminal, then hand its outcome back. A success terminal lets the next line run; an error terminal throwsWorkflowError; a cancel terminal throwsWorkflowCancelled. That makes a child workflow a step you can wrap in an ordinarytry/catchand compensate — the Saga pattern.
await carries meaning only on Workflow.Run — it is the one place in Osy# where you wait. Everywhere else effects
run in place, so await is never written.
Signature#
Workflow.Run(order) // fire-and-forget — start it, carry on; returns nothing
await Workflow.Run("fulfil", order) // wait for the started workflow's terminal, then return / throw on its outcomeentity is an entity-typed value whose type has exactly one workflow bound to it in the same unit. More than one is a
compile error (the target is ambiguous); none is a compile error (nothing to start).
Description#
A workflow is bound to an entity type (Tracks = <Entity>.<Enum>;). Workflow.Run(order) starts that workflow on the
given order row. Use the bare form when the started workflow runs independently — you don't need its result:
var welcome = new WelcomeEmail { Customer = this.Item };
Workflow.Run(welcome); // kick it off; this flow carries onUse the awaited form when the started workflow is a step whose outcome you act on — the essence of a Saga. The
started (child) workflow's terminal surfaces at the await:
- a
successterminal → theawaitreturns and the next line runs; - an
errorterminal → theawaitthrowsWorkflowError; - a
cancelterminal → theawaitthrowsWorkflowCancelled.
Both faults carry the terminal state's message, and both are ordinary catchable exceptions — so a compensating flow is
just try/catch:
try {
await Workflow.Run("payment", payment); // hold until the payment workflow reaches a terminal
goto Confirmed; // reached only if it SUCCEEDED
} catch (WorkflowError e) {
await Workflow.Run("refundHold", new RefundHold { Order = this.Item }); // compensate — undo the earlier step
goto Refunded;
}WorkflowError (an error terminal) and WorkflowCancelled (a deliberate cancel) are distinct on purpose: catch them
separately when a cancel is not a failure. A catch (Exception e) still catches either.
Examples#
enum OrderState { Placed, Done }
[Principal]
entity Person {
[Required, MaxLength(200)] string Email;
security { allow read, create when IsAuthenticated; }
}
entity Order {
[Required, MaxLength(60)] string Reference;
OrderState Status; // no default: the workflow owns this field
security { allow read, create, update when IsAuthenticated; }
}
enum NoticeState { Sending, Sent }
entity ShipmentNotice {
[Required] Order Order;
NoticeState Status;
security { allow read, create, update when IsAuthenticated; }
}
workflow NoticeFlow {
Tracks = ShipmentNotice.Status;
Autostart = true;
Initial = Sending;
state Sending { on Complete { goto Sent; } }
terminal success Sent { }
}
workflow OrderFlow {
Tracks = Order.Status;
Autostart = true;
Initial = Placed;
event Ship();
state Placed {
subscribe Ship();
on Ship {
// Fire-and-forget: the notice runs on its own, with its own durability.
// The argument is an entity-typed VARIABLE — `Workflow.Run(new …)` is refused.
var notice = new ShipmentNotice { Order = this.Item };
Workflow.Run(notice);
goto Done;
}
}
terminal success Done { }
}Fire-and-forget — start a notification workflow and move on:
var notice = new ShipmentNotice { Order = this.Item };
Workflow.Run(notice);Awaited step with compensation — the Saga shape:
try {
await Workflow.Run("reservation", reservation); // a child workflow; wait for its terminal
} catch (WorkflowError e) {
goto Rejected; // it failed — route accordingly
} catch (WorkflowCancelled e) {
goto Cancelled; // it was cancelled — a different route
}
goto Reserved; // it succeededNotes#
awaitis the wait, and the only wait.Workflow.Runwithoutawaitnever blocks; withawaitit holds until the started workflow terminates. Writingawaiton anything else is a compile error.- The started workflow is inferred from the entity's type, exactly like Raising a workflow event. Keep one workflow per bound type, or the target is ambiguous.
- The wait is durable. When the started workflow waits — on a human step, a timer, or its own child — the awaiting
flow parks: it is persisted and lifted off the thread, then resumes exactly where it paused when the child
reaches a terminal, even across a restart. You write straight-line
await; the platform owns the pause. Because it parks, the compensatingawaitinside acatch(or afinally) works too — the wholetry/catchsurvives the wait.
See also#
- Workflows that outlive the code that started them — what happens to a run parked on an awaited child when you deploy past it
- Raising a workflow event — send a typed event to a running workflow.
- <span class="planned" title="this page is planned and not written yet">workflow-goto</span> — transition within a workflow body.
- For(entity).Audit — read a workflow's timeline of events.
- Step labels (naming a child run so it survives a new version) — naming an awaited child run so a parked run survives a new version.