Skip to content

Event modeling: a practical guide for event sourcing

The Cratis Stack

Design the flow before you write the code

The hard part of an event-sourced system is not the plumbing. It is deciding what the events are, where commands come from, and which read model each screen needs. Get that wrong and you find out late, in code. Event modeling lays the whole flow out on one timeline first, and because the timeline’s pieces map onto Cratis’s building blocks, the model you draw becomes the blueprint for your slices.

Event modeling is a method for designing an information system by drawing how information flows through it over time. You lay out a timeline from left to right: the screens people use, the commands they issue, the events those commands record and the read models the next screens display. The result is one picture that a domain expert, a designer and a developer read the same way.

It is for teams that build event-sourced or otherwise event-driven systems and want the domain expert in the design, not only in the requirements. Adam Dymitruk created the method, and eventmodeling.org (read 2026-10-19) is its primary source. This page describes the method in short and shows how Cratis supports it end to end. The notation and the seven steps are Dymitruk’s; the mapping to Cratis is Cratis’s own.

You read a model like a comic strip, one frame after another. The vocabulary is small: wireframe, command, event, read model, processor. There are no class diagrams and no database schemas, only the flow of facts.

UI/A: ReservationsUI/A: ReservationsUI/A: ReservationsC/RM: ReservationsC/RM: ReservationsC/RM: InventoryStream: ReservationsStream: Inventory
ReserveBookScreen
ReserveBook
BookReserved
Availability
CatalogScreen
StockKeeper
DecreaseStock
StockDecreased

Read it as a story. A reader reserves a book on a screen (ui); that fires the ReserveBook command (cmd), which records the BookReserved event (evt), an immutable fact. That fact is projected into an Availability read model (rmo) that the catalog screen reads. A processor (pcr) watching reservations issues a follow-up command to decrease stock. The rows are swimlanes, one per bounded context (Reservations, Inventory).

A command is never stored. An event is the record of the fact. A read model is derived from events. Those rules give the diagram its discipline: every screen reads something a read model provides, every read model is fed by events, and every event comes from a command or from another system.

The connections in a model repeat one of four patterns. Once you can see them, a feature becomes a handful of small, known pieces.

PatternShapeWhat it does
Commandui ➜ cmd ➜ evtA user acts; a command validates and records a fact. The write side.
Viewevt ➜ rmo ➜ uiFacts are projected into the state a screen reads. The read side.
Automationevt ➜ pcr ➜ cmdA processor reacts to a fact and issues a command on its own.
Translationexternal evt ➜ pcr ➜ cmdA fact from another context is adapted into this one’s commands.

The first two are the everyday CQRS loop. The last two are how work flows between contexts without direct coupling: a processor watches for a fact and acts. A translation begins on a reset frame (rf) because its triggering event comes from somewhere else:

UI/A: OrdersC/RM: OrdersStream: PaymentsStream: Orders
PaymentReceived
PaymentTranslator
ConfirmOrder
OrderConfirmed

Dymitruk describes seven steps. You can run them in a workshop with sticky notes, on a whiteboard, or in a tool.

  1. Brainstorming. Ask what happens in the business. Write each answer as a past-tense event on a sticky note: BookAdded, BookReserved, BookBorrowed, BookReturned. Do not worry about order yet.
  2. The plot. Arrange the events on a timeline so the story reads from start to finish. Remove duplicates and merge near-synonyms. Gaps show up as missing steps in the story.
  3. The storyboard. Add the wireframes that people use along the timeline, from the first screen to the last. This keeps the model tied to real use.
  4. Identify inputs. These are the commands. For each event that a person causes, add the command that triggers it, placed between the screen and the event.
  5. Identify outputs. These are the read models, or views. For each screen that shows information, add the read model that supplies it, and draw which events feed it.
  6. Apply Conway’s Law. Separate the model into swimlanes, one per system, team or bounded context, so each team owns its own part of the timeline. Then cut each swimlane into vertical slices, each one small enough to build and test on its own.
  7. Elaborate scenarios. Write the rules as examples. For each command or read model, write a few given/when/then examples. Given these past events, when this command arrives, then this event is recorded, or a rejection.

Throughout, check for completeness by walking the timeline. Every screen shows data that exists, and every field in a read model comes from an event that carries it. That check is where the method pays for itself: you find that a screen has no source for a number it displays, or that an event is missing the field a later screen needs, while the fix is still a sticky note.

Brainstorming a library gives you the events BookAdded, BookReserved, ReservationExpired and BookBorrowed. When you add commands and a read model, you notice something. The catalog needs to show how many copies are available, but BookReserved carries only the book and the member. Where does the number of copies come from? The event BookAdded has to record the number of copies. You found the gap before writing code.

The slice for reserving a book then turns directly into code. In Arc and Chronicle, the command is a record whose Handle() returns the event:

using Cratis.Arc.Commands.ModelBound;
using Cratis.Chronicle.Events;
[Command]
public record ReserveBook(EventSourceId BookId, string Member)
{
public BookReserved Handle() => new(Member);
}
[EventType]
public record BookReserved(string Member);

View C# snippet source on GitHub

The read model that the catalog screen reads is described by the events that feed it. A model-bound projection uses attributes on the type itself:

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections.ModelBound;
[EventType]
public record BookAdded(string Title, int Copies);
[FromEvent<BookAdded>]
public record BookAvailability(
[Key] Guid Id,
string Title,
[SetFrom<BookAdded>(nameof(BookAdded.Copies))]
[Decrement<BookReserved>]
int AvailableCopies);

View C# snippet source on GitHub

Both are excerpts. [Decrement<BookReserved>] lowers AvailableCopies by one each time a BookReserved event is projected. See projections and read models for how projections work.

The model tells you which processors you need as well. If a separate inventory slice must act when a book is reserved, for example a StockKeeper processor that issues a DecreaseStock command, that is an automation: a processor that reacts to BookReserved. In Chronicle that is a reactor class implementing IReactor.

This is what makes event modeling more than a whiteboard exercise for Cratis: each block has a Cratis primitive, and the four patterns match the four kinds of vertical slice Cratis organizes code around. You still write the code, but you do not have to invent its shape.

Event-model blockWhat it meansIn Cratis
Wireframe (ui)a screen the user seesa React screen — Components + Arc.React
Command (cmd)an intent to change statea [Command] record with Handle() — Arc
Event (evt)a fact that happenedan [EventType] record in Chronicle
Read model (rmo)state a screen readsa [ReadModel] built by a projection
Processor (pcr)automation that reacts to factsan IReactor in Chronicle
Swimlanea bounded contexta vertical slice / feature folder

And the patterns line up exactly with how a Cratis slice is classified:

Event-modeling patternCratis slice type
CommandState Change — command + events
ViewState View — read model + projection
AutomationAutomation — a reactor that decides and acts
TranslationTranslation — a reactor that adapts events across slices

Both come from the same idea: events are the source of truth and everything else is derived from them. With Arc and Chronicle together, a [Command]’s Handle() can return the event it records; a [ReadModel] declares the events it is built from; an IReactor reacts to a fact and triggers the next step. Draw the timeline and you have named your commands, events, read models, and reactors, in the order they happen.

Event modeling already has the given/when/then shape baked in. The left side of the column is the context, the command is the behavior under test, and the event/read-model blocks are the expected outcome:

Event-model blockSpecification role
Existing events before the commandGiven
Command or reactor in the columnWhen
New event, projected read model, or side effectThen

That is why Cratis leans on BDD-style specifications. Cratis.Specifications.XUnit gives xUnit the Establish() / Because() / [Fact] lifecycle, and Arc and Chronicle add scenarios in the same language: CommandScenario<TCommand> for the command, EventScenario with Given for past facts, ReadModelScenario<TReadModel> for projections, and ReactorScenario<TReactor> for automation.

Testing with Cratis shows a concrete event-model column translated into an executable stack spec, and spec-driven development places those specs in the chain from model to verified code.

Cratis supports the method from the first sticky note to the running system. Each tool has a stated maturity; generated output needs review in every case, and none of the model-first tools is required to use Arc, Chronicle or Components.

StepToolMaturity
Design the model with your teamStudio, a shared place to design and edit event modelsPreview
Write the model as textScreenplay, a declarative .play language for events, commands, read models and rulesExperimental
Render, compile and verify itStage, which compiles Screenplay and verifies specificationsExperimental
Start from an existing systemPrologue, which proposes a model from captured signals for you to reviewExperimental
Build the slicesArc commands and read models, Chronicle events, projections and reactorsSee the capability map

Agents can take part at every step. EventModelers.ai is a visual event modeling platform whose Build Kits turn a slice marked Planned into a tested Cratis vertical slice. AI-native development covers the setup.

To see one rule travel the whole way, from a Screenplay model through an Arc command, a Chronicle read model and a spec, to the frontend, follow Decisions that hold, from model to frontend. To build a single column, follow Build a full-stack feature.

  • A shared language. A domain expert can point at an event and say it only happens after payment. That correction would otherwise show up as a bug three sprints later.
  • Early detection of gaps. A screen with no data behind it, or an event nothing produces, shows on the timeline.
  • Small, buildable slices. Each slice has a clear input and output, so work divides cleanly between people.
  • Examples that become tests. The given/when/then examples are specifications you can run.

Event modeling earns its keep when behavior is interesting: facts accumulate, screens derive state from history, and work flows between contexts. It is overkill when there is not much of a story to tell: a settings form that reads and writes a single row, a static lookup table, a pure request/response with no meaningful event worth keeping. If a feature is plain CRUD over one record, model it as CRUD. Reach for event modeling when the flow, not the storage, is where the complexity lives.

  • It needs the right people in the room. If domain experts do not take part, you model your assumptions.
  • It is a design method, not a specification of everything. It does not describe look and feel, performance targets or infrastructure.
  • Large models need discipline. Without clear swimlanes per context, a big board becomes hard to read.
  • Naming events like commands. ReserveBook is a command. BookReserved is the event. If you cannot use the past tense, you probably have a command.
  • Starting with the database. Model the flow of information first. Tables come last, or not at all.
  • Skipping screens. Without wireframes, you invent read models no one needs.
  • Putting decisions in read models. A read model shows data. Rules belong in the command side.
  • Modelling in isolation. A model one developer drew alone is a design, not a shared understanding.
  • Treating the model as finished. It changes as you learn. Keep it where the team will update it.

Is event modeling the same as event storming?

Section titled “Is event modeling the same as event storming?”

No. Event storming is a workshop format for discovering the events in a domain, which maps to the first steps above. Event modeling goes further. It adds screens, commands and read models on a single timeline and produces a blueprint you can build from.

Do I need event sourcing to use event modeling?

Section titled “Do I need event sourcing to use event modeling?”

Not strictly. The method describes information flow and works for any system. It fits event sourcing most naturally, because events are first-class in both.

A whiteboard or sticky notes work. Cratis Studio is a collaborative environment for designing and editing event models. It is in Preview at app.cratis.studio. Screenplay lets you write the same model as text.

Model one capability or one bounded context at a time. Use swimlanes for contexts and slices for features.

Each block has a Cratis counterpart, listed in Why it fits Cratis. The vertical slices that follow from it are in Arc vertical slices.

Related concepts: what is event sourcing?, projections and read models, keeping decisions correct and specifications.