On Clause¶
An On Clause specifies how to handle a particular kind of message or situation as part of the definition of a handler. An On Clause is associated with a specific message definition and contains statements that define the handling of that message by the handler's parent. The containing Processor is the recipient of the message and the sender of any statements that send messages.
Kinds of On Clause¶
| Clause | Handles |
|---|---|
on command X |
A specific command message |
on event X |
A specific event message |
on query X |
A specific query message |
on result X |
A specific result message |
on init |
Creation and initialization — once, ever |
on term |
Termination — once, ever |
on activate |
Entity rehydration — every time |
on passivate |
Entity eviction — every time |
on quiescence <window> |
Nothing arrived for the whole window |
on other |
A message not otherwise handled |
on init and on term bracket the whole life of a definition. on activate
and on passivate bracket each residency: an entity that is evicted from
memory and later rehydrated passivates and activates repeatedly without ever
being initialized or terminated again.
handler OrderHandler is {
on init { do "load configuration" }
on activate { do "warm the pricing cache for this order" }
on passivate { do "flush the pricing cache" }
on term { do "archive the order record" }
}
on quiescence — when nothing arrived¶
Every other clause fires because a message came in. on quiescence fires
because none did:
handler CartHandler is {
on command ExampleEntityCommand { do "add the item to the cart" }
on quiescence "PT30M" { do "treat the cart as abandoned" }
}
It is instance-scoped: the window is measured per processor instance, and the clock restarts on every message that instance handles. Inside a state's handler it is armed only while that state is active, so a state change disarms it.
The window¶
The window is a duration literal, or a bare path to a Duration-typed
value — a constant, a state field, or a field of the handled
message:
constant IdleWindow is Duration = "PT10M"
repository Sessions is {
handler SessionHandler is {
on quiescence IdleWindow { do "compact the session store" }
}
}
A let cannot be named here: a let is clause-local and the window is part of
the clause header, evaluated before any body runs. The literal is validated
exactly as a correlation timeout is, so "banana"
and "0s" are both Errors, as is a path whose type is not Duration
(handler-quiescence-window-not-duration).
Where it is allowed¶
Any handler-bearing processor may have one — entity, context, adaptor, projector, repository or streamlet. Two rules bound it:
- At most one per handler (
handler-quiescence-duplicate). A handler that wants two different idle behaviours is describing two different windows, and the language makes you pick. - Never inside a
correlation(handler-quiescence-in-correlation). A correlation already bounds itself withtimes out after, which is the projector-specific spelling of the same idea.
Unlike on activate and on passivate, this is an effect block
yield, tell, send, terminate, morph and initiate are all legal
inside on quiescence. Timing out is a real business event — a cart is
abandoned, a session expires — and saying so usually means emitting
something.
Event-sourcing rules are unchanged: in an event-sourced entity, change state only via a yielded event. That is also what stops replay from re-firing the timer.
quiescence is still a legal identifier
It is contextual — recognised only after on. Existing models that use it
as a name keep working.
Binding the Handled Message¶
An on clause may bind a local name to the message it is handling, using
ordinary type ascription — the same rule as let x: T = ... and a field
declaration p1: String, read as "ord has type command PlaceOrder":
on ord: command PlaceOrder {
when ord.total > MinimumOrder then
yield event OrderPlaced(id = ord.id, total = ord.total)
else
error "an order must reach the minimum"
end
}
Within the body the bound name denotes the whole message; a dotted path reaches its fields. The binding is optional, so every model written without it parses to exactly the same structure.
Local name validation
- A local name that shadows an outer definition is legal but draws a Warning
- A binding whose name collides with a field of the message or state is
legal — bare
foois the binding,foo.foois the field — but draws a Warning about the overload - A local name that does not begin with a lowercase letter draws a
StyleWarning, so camelCase like
myCounterstays legal
Message Origins¶
An on clause may also name where the message came from, optionally binding a
local name to the origin:
An origin may be an inlet, a processor, a user, or an epic.
Restrictions by Container¶
Several restrictions are enforced at parse time, so a violating clause cannot enter the model at all:
| Container | Rule |
|---|---|
| Projector | Event-only: on command, on query and on record are rejected. on event and on result are valid. |
on event (anywhere) |
require and error are forbidden — an event has already happened and must always be accepted. |
on activate / on passivate |
Entity-only, and side-effect free: send, tell, yield, morph and become are rejected. |
on quiescence |
At most one per handler, and never inside a correlation — which bounds itself with times out after. |
| Adaptor | A handler with no on other clause is an Error. |
Shadowed clauses
Within one handler, two on clauses handling the same message make the
later one unreachable. That draws a StyleWarning on the later clause.
Clauses are keyed by their resolved message type where it resolves, so
distinct spellings of the same message are caught.
Options¶
timeout (1 argument) and idempotent (0 arguments) may be set on an on
clause's metadata.
Occurs In¶
- Handlers — the handler to which the On clause is applied
Contains¶
flowchart TD
OnClause(["On Clause"]) --> Statement
Statement --> More["the pseudocode body"]
- Statement — the pseudocode run when the message arrives