Domain-driven design · discovery workshop
S8 said: find the boundary by watching the language. Event storming is the workshop that does it, fast, with the whole room and a wall of sticky notes. This sheet runs a real session on a real project, a food-delivery platform, and shows the board grow as you scroll: from a chaotic pile of events to clean bounded contexts you can turn into services. Companion to S8 · Domain-Driven Design.
Model the business as the things that happened, then work backwards to why. A domain expert can always tell you what happens ("the order was accepted", "the meal was delivered"). Lay those events on a timeline, then add what caused each one and what reacts to it. The clusters that form on the wall are your bounded contexts, discovered by the whole room in an afternoon instead of argued about for a quarter.
Every sticky has a meaning by its colour. This is the whole notation, there is nothing else to learn. Keep this legend in your eye; the rest of the page just adds these one colour at a time.
Domain event orange
Something that happened, in past tense. The spine of the whole board.
Command blue
An intention that causes an event. Usually an actor's decision.
Aggregate yellow
The thing a command lands on and that enforces the rules. Emits the event.
Actor small yellow
A person/role who issues a command. The who.
External system pink
A system outside your control that you call or that calls you.
Policy lilac
A reactive rule: "whenever this event, then issue that command". The glue.
Read model green
The view an actor looks at to decide their next command.
Hotspot pink, rotated
A problem, a question, a disagreement. Park it loudly, don't solve it now.
an actor reads a read model, issues a command → a aggregate checks its rules and emits a domain event → a policy reacts ("whenever…") and fires the next command (sometimes through an external system).
Read that sentence left to right and you can read any event-storming board on earth. Everything below is just this loop, drawn for one real domain, one layer at a time.
A food-delivery platform. A customer orders a meal from a restaurant; we charge them, the restaurant cooks it, a courier delivers it, and we settle the money. Simple to picture, but it spans four very different jobs (ordering, money, the kitchen, the road), which is exactly what makes it a good event-storming subject. We have the right people in the room:
In the room
A product manager, two engineers, someone from the restaurant-ops team, someone from payments, and a courier-operations lead. Mixed perspectives on purpose, the disagreements are the gold.
The scope sentence
"From a customer hitting checkout to the money being settled after delivery." One sentence on the wall keeps the storm from boiling the ocean.
Everyone, in silence · 20 min
Hand out orange stickies. Everyone writes domain events in past tense, in parallel, no discussion, no order yet. You want quantity and honesty. The wall looks like a mess on purpose, duplicates and gaps and all.
Board state · just orange events, unordered
Protect the silence and protect past tense. "Payment Authorized", not "authorize payment" (that's a command, a different colour, later). Don't let anyone order the stickies yet, and don't resolve duplicates. The mess is data; two people writing the same event slightly differently is a language disagreement worth seeing.
The whole room · 25 min
Now the room arranges the same events along a single arrow of time. Duplicates merge, gaps get filled out loud ("wait, what happens between accepted and cooked?"). This is where the narrative of the business appears for the first time, and where people quietly discover they disagreed about the order of things.
✓ Board state · events on one timeline
The room argued about one thing: does Payment Authorized come before or after the restaurant accepts? Ops assumed "charge after they accept"; payments assumed "authorize at checkout, capture later". That single ordering question is a real design decision, and event storming dragged it into the open in minute thirty, not in production. We parked it (next step) and kept moving. Payment Captured and Order Rejected aren't on the spine yet, they're reactions and exceptions; they'll attach later.
The whole room · 15 min
Two markups on the same timeline. Hotspots (rotated pink) capture every problem, risk and "it depends" so they stop derailing the flow. Pivotal events (the vertical dividers) mark the moments the process changes phase, these almost always become bounded-context seams.
✓ Board state · + hotspots new + pivotal markers
The two thick orange dividers are pivotal events: Payment Authorized (commerce becomes a committed order) and Meal Cooked (the kitchen hands off to the road). Watch them, they reappear as context boundaries in step 9.
Each pink diamond is a future incident, ADR, or 2am page, named early and cheaply. "Retry double-charges the card?" is the idempotency problem from S6. "Reject after we charged?" forces an authorize-then-capture decision and a refund saga. You don't solve them in the storm, you make them visible so they get owned instead of discovered.
The whole room · 20 min
For each event, ask who made it happen (small yellow actor) and what outside system was involved (pink). This is the first time the wall shows that "FoodHive" is really three different humans and a couple of vendors, not one app.
✓ Board state · + actors & external systems new
Three different actors own three different stretches of the timeline: Customer at the start, Restaurant in the middle, Courier at the end. "Different people own it" was one of S8's four boundary signals, and here it's literally drawn on the wall. The external systems (Payment Gateway, Maps, SMS) flag where you'll want anti-corruption layers so a vendor's model never leaks into yours.
The whole room · 20 min
In front of every orange event, place the blue command that triggered it, the actor's intention. Events are facts; commands are decisions. The pairing reads like a sentence: Customer issues Place Order, the result is Order Placed.
✓ Board state · + commands new
Notice the one command with no human actor in front of it: Authorize Payment is triggered by the system, not a person. That's a clue a policy is firing it automatically, which is exactly step 7.
Engineers lead · 20 min
Between command and event sits the aggregate (big yellow), the thing that takes the command, checks its rules, and emits the event. Name them as the room's nouns. When the same aggregate handles several commands in a row, you've found a cluster, an early sketch of a module.
✓ Board state · + aggregates new
Four aggregates appear, and two of them (Kitchen Ticket, Delivery) each own two commands back to back. That repetition is the wall telling you those belong together. Each aggregate is also a unit of consistency (one aggregate, one transaction, from S8), so "Reject after we charged?" is clearly a cross-aggregate problem (Kitchen Ticket vs Payment), which means a saga, not one transaction. The hotspot and the aggregate map already agree.
The whole room · 20 min
A policy (lilac) is a reactive rule with no human in the loop: whenever this event happens, then automatically issue that command. Policies are how one event in one part of the business kicks off work in another, and they're exactly the domain events you'll publish between services.
✓ Board state · + policies new (shown under the event they react to)
The two missing events from step 2 finally attach as policy reactions: whenever Meal Delivered → Capture Payment resolves the authorize-then-capture question, and a parked policy whenever Order Rejected → Refund Payment is the compensating step that answers "Reject after we charged?". Each lilac note is a place where one part of the business reacts to another without being tightly coupled to it, the loose coupling between contexts, drawn before any code exists.
The whole room · 12 min
An actor doesn't issue a command in a vacuum, they look at something first. The green read model is that view. Adding them closes the loop (read → decide → command → event) and quietly reveals which data each part of the system actually needs to own or subscribe to.
✓ Board state · + read models new (above the actor who reads them)
Live Order Tracking is read by the customer too, the one read model many parties care about. That's a hint it's a published, cross-context view fed by domain events, not owned by any single aggregate.
The whole room · 20 min
Nobody invents the boundaries; you trace the clusters that formed on their own. Group by where the language, the actors, and the aggregates change together, and draw a box. Those boxes are your bounded contexts, and they land exactly on the pivotal events from step 3.
✓ Final board · four bounded contexts emerge new
Ordering
Order aggregate · the customer's view of the order
Payments
Payment aggregate · authorize, capture, refund
Kitchen
Kitchen Ticket aggregate · accept & cook
Delivery
Delivery aggregate · dispatch & deliver
Four bounded contexts (Ordering · Payments · Kitchen · Delivery) discovered, not decreed. The boundaries sit on the pivotal events; the actors don't cross them; each owns its own aggregate and language. The same word "order" even means three things across them (a cart to Ordering, a charge to Payments, a ticket to the Kitchen), which is S8's "same word, different model" signal, made literal. The room agreed in an afternoon, with the restaurant-ops and payments people in the room, instead of three teams discovering the seam by colliding in production.
Each bounded context becomes a candidate service or module, owning its aggregate and its data, talking to the others only through the domain events on the wall. The board is the first draft of the architecture and the input to ADR #2.
| Bounded context | Owns (aggregate) | Emits (domain events) | Reacts to (policy) |
|---|---|---|---|
| Ordering | Order | Order Placed | starts the flow (customer command) |
| Payments | Payment | Payment Authorized, Payment Captured, Payment Refunded | Order Placed → authorize · Meal Delivered → capture · Order Rejected → refund |
| Kitchen | Kitchen Ticket | Order Accepted, Order Rejected, Meal Cooked | Payment Authorized → present to restaurant |
| Delivery | Delivery | Courier Assigned, Meal Picked Up, Meal Delivered | Meal Cooked → assign courier |
This table is ADR #2's evidence. "We split Payments from Ordering because the language, the actor, the data lifecycle, and the failure mode all change at Payment Authorized, and the only coupling is two domain events." That's a service boundary justified by a real seam, not the org chart, which is exactly what P3 · ADR #2 asks for. Whether each context is a service or just a module is still the S7 decision (start as modules, earn the split), but event storming gave you the lines to choose from.
The logistics that make the difference between a great session and a stalled one.
Set it up
The three levels of zoom
The traps
Interview Q&A
Q · What is event storming, in one breath?
A workshop that maps a domain as a timeline of past-tense events on sticky notes, then adds commands, actors, policies and aggregates, until bounded contexts emerge from the clusters. It's the fastest way to find service boundaries with the business in the room.
Q · Why events first, not data models?
Domain experts can always narrate what happened; nobody argues with "the meal was delivered". Events are the shared language; schemas are a translation that loses meaning. Start where agreement is cheap.
Q · How does it produce boundaries?
Clusters form where the language, the actor, and the aggregate change together, on the pivotal events. You trace the box around each cluster; that's a bounded context, and a candidate service that talks to the rest through domain events.
Storm the events, add the cause and the reaction, and the boundaries draw themselves. Event storming is S8's "find the seam by listening" turned into a room full of people and a wall of stickies, the cheapest way to turn business knowledge into an architecture everyone already agrees on.
Companion to S8 · Domain-Driven Design and P3 · ADR #2. Further reading: EventStorming.com (Alberto Brandolini) · Event storming & DDD primer · Fowler · Bounded Context · Introducing EventStorming (book).