-
Notifications
You must be signed in to change notification settings - Fork 0
Events
Every event that this state model can receive is declared up front, in one block, before any states are defined. The block has two sections, naming how the event reaches the state machine.
Here is the events block from the Door lifecycle:
interaction events
Door opened
Passenger open
Passenger close
Time to close
Hold released
Door closed
Lock
Unlock
Door blocked
completion events
Open delay canceled
Keep trying
Cannot close door
--
And the grammar:
// Events
events = interaction_events? completion_events? block_end
interaction_events = interaction_events_header event_spec*
interaction_events_header = "interaction events" EOL*
completion_events = completion_events_header event_spec*
completion_events_header = "completion events" EOL*
event_spec = INDENT event_name EOL*
event_name = name
An unindented interaction events or completion events keyword opens each section, and each event follows
on its own indented line. One unindented -- closes the whole block, not one per section — the two
headers are sections of a single events block, the same way transitions, ignore and can't happen are
sections of a single state block. See State block grammar.
Both sections are optional. A state model with nothing but interaction events simply has no
completion events header.
An event name is just a name — see Names — so Time to close and Cannot close door are each
a single event, not three or four words of something. My convention is to name events for what has happened,
in the past tense where it reads naturally (Door opened, Door closed) or as a directive when one object
is telling another to do something (Lock, Unlock, Keep trying).
A completion event signals that a state's activity has finished so the state machine can move on. It is generated by a state's own activity and directed at itself:
state Count block
activity
Close attempts.increment
(Close attempts > /R4/R2/Bank.Max close attempts) ? Cannot close door -> me : {
Blocked.set
Keep trying -> me
}
Keep trying and Cannot close door exist only to carry Count block forward to its next state. Nothing
outside the Door lifecycle ever sends them, so they are declared as completion events.
An interaction event arrives from somewhere else — another state model, another domain, an external
entity. Door opened comes from the physical door, Lock from the Cabin.
Most events are clearly one or the other, but nothing stops a state from generating an event that can
also arrive from outside. The Door's Hold released is exactly that case:
state HOLDING OPEN
activity
!Held ? Hold released -> me
transitions
Hold released > CLOSING
If the door is not being held, HOLDING OPEN releases itself immediately and that use is a completion. If
it is being held, the state waits and the release arrives from outside.
An event is a completion event only when that is its sole use. Anything that can also arrive from
outside is an interaction event, even where some state generates it for itself. Classification really
belongs to the dispatch rather than to the event, so the broader use has to win. A state machine that
treated Hold released as a completion event would be entitled to assume it can never arrive in any other
state, and that is not true.
Because it decides what a missing response means. An event with no declared response in a state can't happen — but for the two kinds of event that verdict is reached very differently:
- A completion event cannot be received in any state other than the one that issued it. That follows from the definition, so there is nothing to explain and nothing for the modeler to decide.
- An interaction event can arrive at any time. If a state has no declared response to one, that is a decision nobody has made yet, not a systematic fact.
Declaring the split lets tooling tell those two apart and report the second as unfinished work. Without it, an undecided response looks exactly like an impossible one.
Because the declaration is the model's contract. Any event a state machine can receive is listed here, once, whether it's sent from another state model, from an external domain, or by the state machine to itself. That gives a downstream module a complete picture of what this state machine responds to without having to scan every state, and it gives you a place to notice that you've accidentally invented two names for the same event.
The event names appearing in each state's Transitions, Ignore and Can't Happen sections must all be drawn from this block.
Three fields on StateModel_a:
-
interaction_eventsandcompletion_events— the two sections, each a list of event names -
events— every declared event in declaration order, interaction events first
Copyright 2023-2026 © Leon Starr under MIT Open Source License