Behavioural View design intermediate

Event Model

The business as a sequence of facts that happened, with the command that caused each and the read models and reactions it feeds.

flowchart LR
  subgraph t1[" "]
    direction TB
    c1["cmd: Place Order"]
    e1["OrderPlaced<br/><i>fact</i>"]
    r1[("read: Order Status")]
    c1 --> e1 --> r1
  end
  subgraph t2[" "]
    direction TB
    c2["cmd: Authorise Payment"]
    e2["PaymentAuthorised<br/><i>fact</i>"]
    r2[("read: Payment Ledger")]
    c2 --> e2 --> r2
  end
  subgraph t3[" "]
    direction TB
    c3["policy: on PaymentAuthorised<br/>allocate stock"]
    e3["StockAllocated<br/><i>fact</i>"]
    r3[("read: Availability")]
    c3 --> e3 --> r3
  end
  subgraph t4[" "]
    direction TB
    c4["policy: on StockAllocated<br/>request despatch"]
    e4["ConsignmentCreated<br/><i>fact</i>"]
    r4[("read: Tracking")]
    c4 --> e4 --> r4
  end
  e1 --> c2
  e2 --> c3
  e3 --> c4

What it is

Time runs left to right. Each column holds a command (an intention), the event it produced (a fact, always past tense), and the read model that fact updates. Policies are the reactions that turn one fact into the next command, and they are where the business rules actually live.

It comes out of an event storming workshop and is the bridge between that workshop's sticky notes and a design somebody can build.

When you produce it

Before choosing service boundaries in an event-driven system. The clusters of events that always move together are usually the boundaries, and they are often not the ones the org chart suggests.

Who reads it

The whole team, including domain experts — an event model that domain experts cannot read has failed at its main job, which is agreeing what happens.

What good looks like

  • Events are past tense and business-meaningful. OrderPlaced, not OrderTableUpdated.
  • Commands and events are visually distinct; conflating them hides where a request can be refused.
  • Policies are named and shown as the trigger for the next command.
  • Read models appear, so the query side is designed rather than discovered.

Common mistakes

  • CRUD events. CustomerUpdated carries no business meaning and forces every consumer to diff.
  • Modelling only the happy path, so compensation is never designed.
  • Treating database change events as domain events. They are not; one is an implementation detail of a table, the other is a published contract.