doc-hub · event storming
The .eventstorm format
Plain text, and the file the board reads and writes. The technique itself is written up in the development hub.
An example
// Event storm exported by doc-es.
// Comments and blank lines in an imported file are not preserved: the board
// is the source, this file is a render of it.
eventstorm "Ordering a pizza" {
product "client-onboarding"
level process-modelling
lane "Customer" {
actor "Hungry customer" @1
event "Menu opened" @1
event "Pizza added to the basket" @2
event "Basket emptied and started again" @2 {
note "Happens more than anybody expected. Worth\
understanding before it is designed away."
}
event "Order placed" @3
event "Pizza delivered" @8
}
lane "Payments" {
command "Take the payment" @3
event "Payment requested" @4
system "Payment provider" @4
policy "Whenever a payment is refused, hold the order" @5
event "Payment refused" @5
event "Payment accepted" @5
hotspot "Nobody agrees whether a refused payment cancels the order" @5
}
lane "Kitchen" {
readmodel "Orders waiting" @6
event "Order sent to the kitchen" @6
actor "Kitchen staff" @6
event "Pizza put in the oven" @6
opportunity "Tell the customer when it goes in the oven" @6
event "Pizza handed to the driver" @7
}
}
A process model: three lanes over one timeline, notes stacked at column 5, a gap in the customer’s lane while the others are busy, and a hotspot nobody has resolved. The payment lane carries the level’s whole point — a command, the event it causes, and the policy that reacts to it.
What the format decides for you
- One storm, and one wall
- A file holds one
eventstormblock. A second is an error rather than a merge: two walls in one file is almost always a bad paste. A storm always has at least onelane, even when nobody has named one — the practice starts with paper on a wall, and the board needs a row to put the first note on. - The keyword is the colour
- A card is written with the keyword of its kind —
event,actor,system,hotspot,opportunity— and there is no separate colour or type annotation. A card whose keyword said one thing and whose annotation said another would be a state the file could express and the board could not. - The board is a grid: lanes down, time across
- A
laneis a horizontal swimlane — a department, an actor, a subsystem — and@columnon a card says where along the timeline it sits. Column 4 is the same moment in every lane, which is what lets two notes side by side mean *simultaneous*, and what lets a lane have a visible gap where its neighbour is busy. - A column is a coordinate, not a list position
- Which is why cards carry
@3where the other two boards refuse index fields. There, a card’s position *was* its place in a list, so an index would have been a second copy of the same fact. Here the board is two-dimensional and the column is the only record of when a note happens. Lane order is still a list, and still has no index. - Several notes may share one square
- A moment often turns out to involve an actor, a system and an event at once, so notes stack at one point of one lane. They are written with the same
@columnand keep the order they appear in — the only record of a stacking order there is. @columnmay be left out, and is always written back- A card with no
@takes the square after the last one written in its lane, so a run of events typed straight down needs no numbering. Export always writes the number out: the coordinate is the fact, and leaving it implicit would make a file’s meaning depend on the order of the lines around it. - The board is meant to feel endless
- There is always one empty column past the rightmost note and a control to add a lane under the last. Reaching the end creates the next square. It is not literally unbounded — the grid draws what is used plus one, with a floor so a fresh board looks like a board — because genuine infinity means a virtualised grid, and that would cost the thing that makes a wall readable: seeing all of it at once.
- Cards may be written before any lane
- Chaotic exploration produces a heap of events long before anybody agrees which lane they belong to. So a card is legal at the top level, and every such card is gathered into one unnamed lane on import. Export then writes that lane out explicitly — the one place this format normalises rather than renders.
- Three levels, and the notation is cumulative
level big-picture,level process-modellingorlevel software-design. Big picture has six colours,contextamong them — finding the seams is the last phase of a big picture, not a software-design activity. Process modelling addscommand,policyandreadmodel; software design addsaggregateandui— the component, and the screen a person decides on, which completes the human pathread model → screen → command. Each level keeps everything the shallower ones have — a process model still has domain events and hotspots on it, it has *more*, never different.- The level is declared, not inferred
- It is a statement of intent: a session that has decided it is modelling a process decided that before placing its first command. Inferring it from the cards would mean the level changed under the room as somebody added one, and a facilitator could never set it up in advance. Omitting it means
big-picture, which is where the practice starts — and is why files written before this setting existed still open. - A card the level does not admit is an error
- Not a silent promotion. Quietly deepening the level because somebody wrote one
commandwould change what the file claims about itself without anybody deciding to. The message names the level that *would* admit the card, so the fix is one word and the error says which. The board enforces the same rule from the other side: it will not let the level be lowered past the notes already on the wall. - Braces, not indentation
- Whitespace is a formatting choice and never syntax, so a file that has been through a chat window or a different editor still parses.
- Notes wrap at 50 characters
- A note is prose. A trailing backslash carries its string onto the next line, and that split is the break — one pair of quotes for the whole note.
- Comments do not survive the board
- A // comment is read and discarded. Import a commented file, export it again, and the comments are gone: the board is the source, the file is a render of it.
The grammar
File = EventStorm , EOF ;
EventStorm = 'eventstorm' , String ,
[ '{' , { Product | Level | Lane | Card | Note } , '}' ] ;
Product = 'product' , String ; (* at most one *)
Level = 'level' , ( 'big-picture' | 'process-modelling'
| 'software-design' ) ; (* at most one; default big-picture *)
Lane = 'lane' , String , [ '{' , { Card | Note } , '}' ] ;
Card = Kind , String , [ Column ] , [ '{' , { Note } , '}' ] ;
Column = '@' , Integer ; (* one-based; defaults to the next square *)
Kind = 'event' | 'actor' | 'system' | 'hotspot' | 'opportunity'
| 'context' (* big picture *)
| 'command' | 'policy' | 'readmodel' (* process modelling *)
| 'aggregate' | 'ui' ; (* software design *)
Note = 'note' , String ;
String = '"' , { Char | Escape | Splice } , '"' ;
Escape = '\\' , ( '"' | '\\' | 'n' | 't' ) ;
Splice = '\\' , newline , { space } ; (* carries the string on; is a break *)
Comment = '//' , { Char } ; (* discarded *)