Event storming, worked on a real domain

What it is

Event storming is a workshop for discovering a domain model with the people who understand the domain in the room. It produces a shared language and a set of candidate boundaries in hours, using sticky notes, rather than in weeks, using documents nobody reads.

The grammar is fixed and it is what makes the workshop converge:

ORANGE   DOMAIN EVENT       something that happened, past tense
                            "Order Placed", "Payment Captured"
BLUE     COMMAND            an intent that causes an event
                            "Place Order"
YELLOW   ACTOR              who issues the command
PINK     EXTERNAL SYSTEM    something outside your control
PURPLE   POLICY             "whenever X happens, do Y"
GREEN    READ MODEL         the information someone needs to
                            decide
RED      HOT SPOT           a disagreement, an unknown, a risk.
                            The most valuable colour.
BEIGE    AGGREGATE          the consistency boundary that
                            receives commands and emits events

Events go on the wall first, in time order, and everything else is discovered by asking questions about them. That ordering is the method: you cannot argue about an aggregate boundary before you agree on what happens.

What this is confused with: a design meeting. A design meeting starts from a proposed solution. An event storm starts from what happens in the business and refuses to discuss solutions until the timeline is agreed, which is why domain experts can participate and usually dominate the first hour.

Also confused: event storming and event sourcing. The workshop is useful whether or not you store events. Most event storms lead to a conventional CRUD system with better boundaries, and treating the technique as a commitment to event sourcing is the most common reason it gets rejected.

The problem it solves

Two failures, and the second is the expensive one.

Nobody shares a definition.

"Order" in one company, six meanings found in a single
workshop:

  sales      a signed commitment, may not be paid
  finance    a revenue-recognisable transaction
  warehouse  a picking instruction, may be split into three
  support    a customer's whole purchase, including returns
  the app    a row in the orders table
  the API    a JSON document with 60 fields

Every integration bug between those teams for three years had
been a translation error nobody could name, because everyone
used the same word.

And the boundaries get drawn by the database.

Without a shared model, services get drawn around tables:
OrderService, CustomerService, AddressService, PaymentService.

So placing an order becomes a distributed transaction across
four services owned by one team, and every business change
touches all four.

The alternative, drawing them around business capabilities,
requires knowing what the business capabilities ARE, which is
what the workshop produces.

Mechanics

Preparation

WHO, and this is the part that decides whether it works:
  - 2 to 4 domain experts who actually do the work. Not their
    manager's description of the work.
  - the engineers who will build it
  - a product owner
  - ONE facilitator who does not have a stake in the answer
  - 6 to 12 people total. Above about 15 it stops converging.

SPACE: a very long wall, or an unlimited digital canvas
  (Miro, FigJam). The single most common physical failure is
  running out of wall, so plan for twice what you expect.

TIME: 2 to 4 hours for a first "big picture" pass on one
  business flow. A full domain takes several sessions.

RULES stated at the start:
  - events are PAST TENSE. "Order Placed", not "Place Order"
    or "Ordering".
  - no discussion of technology, database design, or services
    until the timeline is agreed
  - a disagreement becomes a RED sticky, not a debate. This is
    the rule that keeps the room moving.

The past-tense rule does more work than it looks like. "Order processing" is a process nobody can place on a timeline; "Order Placed", "Payment Captured", "Stock Reserved" are things that either happened or did not, so they can be ordered, and disagreement about their order is immediately visible.

The phases

PHASE 1: CHAOTIC EXPLORATION (30-45 min)
  Everyone writes orange event stickies simultaneously, in
  silence, and puts them on the wall roughly in time order.
  No discussion. Duplicates are fine and informative.
  Output: a messy wall of 100 to 300 events.

PHASE 2: ENFORCE THE TIMELINE (45-60 min)
  Walk the wall left to right as a group. Deduplicate. Order.
  Argue about sequence.
  Every disagreement gets a RED sticky rather than a
  resolution. "Does stock get reserved before or after payment
  authorisation?" is often a genuine business question nobody
  has decided.
  Output: a single agreed timeline, plus 10 to 30 hot spots.

PHASE 3: COMMANDS, ACTORS, SYSTEMS (45 min)
  For each event: what caused it? A user command, a policy, or
  an external system? Add blue, yellow, pink.
  Output: causation, and the discovery of events nobody causes
  (usually a missing command) and commands that cause nothing
  (usually a missing event).

PHASE 4: POLICIES AND READ MODELS (45 min)
  Purple: "whenever Payment Failed, notify the customer and
  release the stock reservation after 30 minutes." Policies are
  where most of the real business logic hides.
  Green: what does the actor need to SEE to issue this command?
  Output: the reactive rules, and the queries that matter.

PHASE 5: AGGREGATES AND BOUNDARIES (60 min)
  Group commands and events that must be consistent together.
  Each group is a candidate aggregate.
  Then look for the seams: places where the language changes,
  where a different team owns the process, where the same word
  means something different.
  Output: candidate aggregates and candidate bounded contexts.

Phase 2 is where the value is, and it is the phase people cut for time. The argument about whether stock is reserved before or after payment is the design conversation, held in business terms, with the people who know the answer.

Reading the wall

THE PIVOTAL EVENTS. A handful of events that everything else
  organises around: "Order Placed", "Payment Captured",
  "Shipment Dispatched". Draw a vertical line at each. Those
  lines are candidate bounded-context boundaries, because they
  are where the process hands over.

WHERE THE LANGUAGE CHANGES. If the stickies to the left say
  "customer" and to the right say "consignee", you have crossed
  a context boundary. This is the single most reliable
  boundary signal on the wall.

CLUSTERS OF RED. A dense group of hot spots is either the
  highest-risk part of the system or the part nobody
  understands, and both are worth attention before code.

EVENTS WITH NO COMMAND. Something happens and nobody knows
  what causes it. Usually an external system, a scheduled job
  nobody documented, or a genuine gap.

COMMANDS WITH NO EVENT. An action with no consequence, which
  usually means a feature nobody uses or a step that is
  actually two.

A LONG CHAIN OF POLICIES. "Whenever A then B, whenever B then
  C, whenever C then D" is a saga, and identifying it here is
  much cheaper than discovering it in production.

Turning the wall into a design

AGGREGATE  = a group of commands and events that must be
             transactionally consistent. It is the consistency
             boundary, and it should be as SMALL as the
             invariants allow. If two things do not have to be
             consistent in the same instant, they are two
             aggregates joined by a policy.

BOUNDED    = a region of the wall with its own language and,
CONTEXT      usually, its own team. The candidate boundaries
             are the pivotal events and the language changes.

POLICY     = either an event handler (asynchronous) or, if the
             coupling is genuinely synchronous, a step inside
             the aggregate.

READ MODEL = a projection, or just a query. Green stickies are
             where CQRS becomes a question worth asking.

CONTEXT MAP: how the contexts relate. Customer/supplier,
  conformist, anti-corruption layer, shared kernel. The ACL is
  the one you will actually need, at the boundary with the
  legacy system or the external partner.

The most useful output is usually not the model, it is the hot spots and the shared vocabulary, because the model will change and the vocabulary is what makes the next conversation cheaper.

A worked example: a subscription box company

An eight-person engineering team, a monolith, and a proposal on the table to split into microservices drawn as SubscriptionService, CustomerService, PaymentService, ShipmentService and ProductService. The workshop was run before committing.

The room: two operations staff who packed and dispatched boxes, one customer-support lead, the finance controller, the product manager, four engineers, and an external facilitator. Four hours, one flow: "a customer subscribes and receives their first three boxes."

Phase 1 and 2, the timeline as it settled:

Subscription Requested -> Payment Method Verified -> Subscription
Activated -> Billing Cycle Opened -> Payment Captured -> Box
Contents Selected -> Stock Reserved -> Box Packed -> Shipment
Dispatched -> Delivery Confirmed -> Billing Cycle Closed
-> [next cycle]

...plus the branches:
Payment Failed -> Retry Scheduled -> Payment Failed ->
  Subscription Suspended
Stock Insufficient -> Substitution Applied -> (or)
  Box Deferred
Delivery Failed -> Redelivery Scheduled -> Return To Sender ->
  Refund Issued

Three findings from phase 2, before any technology was discussed:

1. THE BIGGEST ARGUMENT: does "Box Contents Selected" happen
   before or after "Payment Captured"?

   Engineering assumed after (charge, then pick).
   Operations said before, always: contents are selected on the
   15th based on stock, and billing runs on the 20th, because
   a customer must be able to see and swap their box before
   being charged.

   This was not written down anywhere. The existing system did
   it the engineering way, and operations had been working
   around it with a spreadsheet for two years.

   RED sticky, resolved in the room by the product manager:
   selection precedes billing.

2. "Subscription Suspended" and "Subscription Cancelled" were
   used interchangeably by engineering and were completely
   distinct to finance: a suspension retains the customer
   record and the revenue forecast; a cancellation removes it
   and triggers a different retention flow.

   The database had one boolean.

3. AN EVENT WITH NO COMMAND: "Box Deferred". Nobody could say
   what caused it. It turned out to be a manual database update
   an operations person made when stock was short, roughly 40
   times a month, unrecorded and untracked.

"Operations had been working around the system with a spreadsheet for two years and nobody in engineering knew" is the finding that justifies the workshop by itself, and it surfaced in the first hour because the timeline forced the sequence to be stated out loud.

Phase 4, the policies, which is where the business logic was:

Whenever Payment Failed
  -> schedule a retry at +1d, +3d, +7d
  -> after the third failure, Subscription Suspended
  -> notify at each step, with different copy

Whenever Stock Insufficient
  -> if a substitution exists within the same category and
     price band, Substitution Applied
  -> else Box Deferred, and the billing cycle does NOT charge
  -> and the customer is notified BEFORE the charge date, which
     is why selection must precede billing (see finding 1)

Whenever Delivery Failed twice
  -> Return To Sender, Refund Issued, and the next cycle is
     paused pending an address confirmation

Whenever Subscription Suspended for 30 days
  -> Subscription Cancelled, and a win-back campaign starts

None of this existed in any document. Two of the four policies were implemented differently in the code from how operations described them, which was the source of a recurring class of support ticket.

Phase 5, the boundaries, and the reason the original plan was abandoned:

Language changes on the wall:

  left of "Subscription Activated":  prospect, plan, trial,
                                      offer
  between "Billing Cycle Opened" and
    "Payment Captured":               invoice, charge, dunning,
                                      revenue
  between "Box Contents Selected" and
    "Box Packed":                     SKU, pick list, tote,
                                      substitution
  right of "Shipment Dispatched":     consignment, consignee,
                                      carrier, tracking

Four distinct languages, and each one had a different set of
people who spoke it fluently.

Candidate bounded contexts:
  SUBSCRIPTION   (the customer's commitment and its lifecycle)
  BILLING        (cycles, invoices, dunning, revenue)
  FULFILMENT     (selection, stock, picking, packing)
  DELIVERY       (carriers, tracking, exceptions)

Compare to the proposed services:
  SubscriptionService, CustomerService, PaymentService,
  ShipmentService, ProductService

The proposal had FIVE services drawn around nouns. The wall
showed FOUR contexts drawn around processes, and "Customer"
and "Product" were not contexts at all: both appeared in every
context with a different shape.

"Customer" and "Product" being present in all four contexts with different meanings is the classic result, and it is the argument against entity services in a form the room could see rather than a principle someone asserted.

The aggregate discovery, which changed the data model:

Grouping commands and events that must be transactionally
consistent:

  Subscription     activate, suspend, cancel, change plan
  BillingCycle     open, apply charge, close.
                   ONE PER CYCLE, not one per subscription.
  Box              select contents, substitute, defer, pack.
                   ONE PER CYCLE PER SUBSCRIPTION.
  Shipment         dispatch, track, fail, return

The existing model had ONE Subscription row carrying the
current cycle's state, the current box's contents and the last
shipment's status, all as columns.

Consequence of that design, which the team recognised
immediately: two operations staff editing two different boxes
for the same subscription conflicted, because they were writing
the same row. This was a known bug filed as "optimistic
concurrency errors in the ops tool" with no known cause.

Splitting BillingCycle and Box into their own aggregates
removed it by construction.

A known unexplained bug being explained by an aggregate boundary is the strongest possible demonstration that the boundary was wrong, and it took a wall of stickies rather than a debugging session.

What was actually built:

NOT five microservices. A modular monolith with four modules
matching the four contexts, each with its own schema and no
cross-module foreign keys, communicating by in-process events.

Rationale recorded in an ADR: eight engineers, four contexts,
no independent scaling requirement, and no team boundary that
would justify independent deployment. The module boundaries
give the option to extract later, and the event contracts are
the same either way.

Two years later, one module (DELIVERY) was extracted to a
service, because a partner integration gave it a different
deployment cadence. The extraction took three weeks, because
the boundary and the event contract already existed.

The four hours produced the boundary that made a three-week extraction possible two years later, which is the return the workshop is actually for.

Measured afterwards:

                                    before      after 12 months
support tickets caused by the
  suspension/cancellation
  ambiguity                       ~14/month           0
"optimistic concurrency" bugs in
  the ops tool                    ~9/month            0
manual database edits by ops      ~40/month           0 (Box
                                                       Deferred
                                                       became a
                                                       real
                                                       command)
services deployed                     1                1 (then 2)

The 40 manual database edits a month becoming a real command is the outcome that operations noticed, and it came from a single sticky note that nobody could explain the cause of.

Two things that went wrong in the workshop:

1. The first hour was almost lost to a technology argument. An
   engineer began sketching a service diagram in phase 1. The
   facilitator's rule ("no technology until the timeline is
   agreed") had been stated and not enforced.
   Re-stated firmly, and the rule held afterwards. The lesson:
   the facilitator's only real job is enforcing that one rule,
   and a facilitator with a stake in the answer will not.

2. The team ran a second session for a different flow with
   19 people, on the theory that more perspectives were
   better. It did not converge: the wall had four parallel
   conversations and no shared timeline, and it was abandoned
   after two hours.
   Re-run with 9 people and it worked. Above roughly 12 to 15
   the format stops functioning, and the fix is more sessions
   rather than a bigger room.

Production evidence

Event storming was created by Alberto Brandolini and is documented in his book Introducing EventStorming, which specifies the sticky-note grammar, the phase structure and the facilitation rules described above, including the past-tense constraint and hot spots as first-class output.

Domain-driven design's strategic patterns (Evans, Domain-Driven Design; Vernon, Implementing Domain-Driven Design) define bounded contexts, ubiquitous language, aggregates as consistency boundaries and the context map relationships that an event storm's output maps onto directly.

"Aggregates should be small" is Vernon's published rule of thumb, with the reasoning that an aggregate is a transactional consistency boundary and every additional entity inside it increases contention, which is exactly the mechanism behind the concurrency bug in the worked example.

Team Topologies (Skelton and Pais) supplies the organisational half: bounded contexts that do not match team boundaries produce coordination cost, which is why "who owns this part of the wall" is a question worth asking during phase 5.

The argument against entity services appears throughout microservices literature, including Sam Newman's work, on the grounds that a service per noun makes every business operation a distributed transaction; an event storm produces the process-shaped alternative as a by-product.

Remote event storming on digital canvases (Miro, FigJam) became standard practice from 2020 onward, and the practical trade is documented in community write-ups: unlimited wall space and automatic capture, against a measurably harder time achieving the simultaneous chaotic-exploration phase.

The debate

Is event storming worth four hours of eight people? For a domain nobody has modelled and where teams use the same words differently, decisively: in the worked example it surfaced a two-year operational workaround, an ambiguity causing fourteen support tickets a month, and the cause of a known unexplained bug. For a well-understood domain with an existing shared language, it is a nice whiteboard session and not a discovery, and the honest signal is whether anyone can predict what the hot spots will be.

Does it require event sourcing? No, and believing it does is the most common reason it gets rejected. The workshop discovers events as facts about the business, and most result in a conventional system with better boundaries. Whether you persist the events is a separate decision made much later.

Should domain experts really be in the room? Yes, and it does not work without them. The specific value is that they correct assumptions engineers do not know they are making, and the selection-before- billing finding is the canonical shape: the engineers had a plausible model, it was wrong, and no document would have said so. The cost is their time, which is real and is why one flow at a time is the right scope.

Is the sticky-note grammar necessary or theatre? Necessary, and mostly because of the colours' constraints rather than their aesthetics. Past-tense events can be placed on a timeline and argued about; a process name cannot. Red for disagreement is what stops the room stalling on a debate. What is theatre is insisting on physical stickies when the team is distributed.

How many people? Six to twelve. Above about fifteen the wall fragments into parallel conversations and stops converging, which the worked example demonstrated at nineteen. The fix is more sessions, not a bigger room, and this is counter-intuitive to anyone trying to include every stakeholder once.

Should the output become the code structure directly? The boundaries and the language, yes; the aggregates, provisionally. The wall is a hypothesis with a shared vocabulary attached, and treating its aggregate groupings as final is how a workshop produces an over-fitted model. Build the first slice, learn, and expect one or two boundaries to move.

Follow-up Q&A

"What is event storming and what does it produce?"

A facilitated workshop where domain experts and engineers put past-tense domain events on a wall in time order, then discover the commands, actors, policies, read models and aggregates by asking questions about that timeline. The outputs are a shared vocabulary, a set of candidate aggregates and bounded contexts, and a list of hot spots, which are the disagreements and unknowns. In practice the vocabulary and the hot spots are worth more than the model, because the model will change and the vocabulary makes every subsequent conversation cheaper.

"Why must events be past tense?"

Because a past-tense event either happened or it did not, so it can be placed on a timeline and its position argued about. "Order processing" is a process nobody can order relative to anything else, and the room will discuss it for twenty minutes without converging. The constraint is what makes the workshop terminate, and the disagreements it exposes, such as whether stock is reserved before or after payment authorisation, are usually genuine business questions nobody has decided rather than technical ones.

"What is the single most reliable boundary signal on the wall?"

The language changing. If the stickies on the left say "customer" and the ones on the right say "consignee", you have crossed a bounded context. In one workshop four distinct languages appeared, prospect and plan and trial; invoice and dunning and revenue; SKU and pick list and tote; consignment and carrier and tracking, and each had a different set of people who spoke it fluently. The pivotal events, the handful everything organises around, are the second signal, because they are where the process hands over.

"How does the wall change a service decomposition?"

By replacing noun-shaped boundaries with process-shaped ones. In one case the standing proposal was five services named after entities: subscription, customer, payment, shipment, product. The wall showed four contexts named after processes, and revealed that "customer" and "product" appeared in every one of them with a different shape, which is exactly why entity services turn every business operation into a distributed transaction. What was built was a modular monolith with four modules, and one of them was extracted to a service two years later in three weeks, because the boundary and the event contract already existed.

"How do you find aggregates, and what makes one wrong?"

Group the commands and events that must be transactionally consistent in the same instant; each group is a candidate aggregate, and it should be as small as the invariants allow. If two things do not have to be consistent instantaneously, they are two aggregates joined by a policy. A wrong aggregate shows up as contention: in one case a single subscription row carried the current billing cycle, the current box's contents and the last shipment's status, so two operations staff editing two different boxes for the same subscription wrote the same row. That was a known bug filed as "optimistic concurrency errors" with no known cause, and splitting billing cycle and box into their own aggregates removed it by construction.

"What are the failure modes of the workshop itself?"

Two, and both were hit in one team. Letting a technology discussion start before the timeline is agreed, which the facilitator's single real job is to prevent, and which requires a facilitator with no stake in the answer. And inviting too many people: at nineteen the wall fragmented into four parallel conversations and never converged, and re-running it with nine worked. Above roughly twelve to fifteen the format stops functioning, and the fix is more sessions rather than a bigger room, which is counter-intuitive when you are trying to include every stakeholder once.

Common misconceptions

"Event storming means we are doing event sourcing." The workshop discovers events as business facts. Most results are conventional systems with better boundaries, and persistence is a separate decision.

"It is a design meeting with sticky notes." A design meeting starts from a proposed solution. An event storm refuses to discuss solutions until the timeline is agreed, which is why domain experts can lead the first hour.

"Engineers can run it without domain experts." Then it produces the engineers' existing assumptions, laid out attractively. The value is the corrections.

"More stakeholders is better." Above about fifteen the wall fragments and stops converging. More sessions, not a bigger room.

"The output is the architecture." The output is a hypothesis plus a vocabulary. Expect one or two boundaries to move once the first slice is built.

"Hot spots are unfinished business." They are the highest-value output: a dense cluster of red is either the riskiest part of the system or the part nobody understands.

Interview delivery note

Say this verbatim: "Events go on the wall first, in past tense, in time order, and nothing about technology is discussed until the timeline is agreed. The past tense is the mechanism: an event either happened or it did not, so its position can be argued about, and the arguments turn out to be business questions nobody had decided." It names the method and the one constraint that makes it work.

The senior-versus-staff separator is reading boundaries off the language rather than off the data model. A senior engineer groups entities. A staff engineer notices that the stickies change from "customer" to "consignee" halfway along the wall, calls that a bounded context boundary, and then points out that "customer" and "product" appear in all four contexts with different shapes, which is the concrete argument against the five entity-named services that were already proposed. That is a design decision made from evidence the whole room can see rather than from a principle asserted.

The second signal is using the wall to explain an existing bug. Saying "the aggregate grouping showed that one subscription row carried the billing cycle, the box contents and the shipment status, which explained a known unexplained optimistic-concurrency bug in the ops tool" demonstrates that the model has predictive power, which is what converts a workshop from a facilitation exercise into an engineering tool.

Further reading

  • Alberto Brandolini, Introducing EventStorming, for the grammar, the phases and the facilitation rules.
  • Eric Evans, Domain-Driven Design, and Vaughn Vernon, Implementing Domain-Driven Design, for bounded contexts, ubiquitous language and the small-aggregate rule.
  • Skelton and Pais, Team Topologies, for why a bounded context that does not match a team boundary produces coordination cost.
  • The DDD strategic design and DDD tactical design pages, which are the vocabulary this workshop produces.
  • The modular monolith vs microservices page, for what to do with the boundaries once you have them.