Event Map Design
This page provides a step-by-step guide of how to design an event-driven system using Continuous Event Storming. The resulting artifact is called Event Map.
Throughout the guide we’ll use an example from a car rental business. Code snippets are taken from a NodeJS + TypeScript application, but of course all concepts can be ported to any programming language or technology stack.
Start from an Idea
Let’s assume we are a startup team that wants to develop a new car rental SaaS from scratch. We just finished our Big Picture Event Storming and now want to get our hands dirty with some product development.
The first module of our new SaaS should be some kind of Fleet Management. We need to be able to Add a Car to our fleet and Create a Rental Offer for it so that it can be booked by customers.
Right now, we are in our first Design-Level Event Storming session. We should look at each event from above and think of it as a feature of the new system.
Where - Identify the Entry Point
In the early version of our SaaS, adding a car will be a manual task. Later we want to add batch imports and derive car details from a third-party service, but for now we keep things simple since we want to have a fast feedback loop.
So we start with a UI Card to sketch a rough idea of a UI screen showing a list of cars in the fleet and a button to add a new car.
Instead of using UI cards, some teams prefer a more visual approach by uploading screenshots from wireframes or prototypes (especially when UX is part of the team). That’s totally fine. The only advice is that those wireframes should not be too detailed as this can become a bottleneck in the flow.
Who - Identify the Actor
Each feature should have a User Story associated to it and for this you need an Actor (aka. Role or Persona).
As a Fleet Manager I want to add a car to the fleet so that it can be booked later.
Each UI card should be accompanied by an Actor Card.
What - Identify the Intention
The Fleet Manager wants to add a car to the fleet. Adding a car is their intention, and they can tell the system about it by triggering a command. Let’s put a Command Card between UI and event to make that clear.
Input - Which Information
As a next step we should define which information is needed for the command to be accepted by the system. In a collaboration session the best way to do that is by using an Information Card per field or property. This allows all participants to contribute by adding cards in parallel.
After a collaboration session you might want to clean up the Event Map and move such information into Card Metadata
How - Command Handling
Some object or function in the system needs to handle the command. And for each one you should ask: Do we need to check some rules when handling the command? If the question is answered with yes, write down the rules on an Aggregate Card.
We work with Event Sourcing in our system and record all events in event streams. Each stream is represented by an aggregate. So even thought we don’t need to check specific rules for this command, we write down the name of the aggregate to know in which stream the Car Added event will be recorded.
Please note: Input or command validation is usually not defined as a business rule nor are validation errors visualized on the Event Map, because that’s implementation detail. Anyway, we recommend using a two-step validation process. Command input should already be validated in the UI (if possible) so that the user gets direct feedback on missing or wrong information. The backend system should validate command input again when receiving the command and before handing it over to an aggregate or handling function. JSON Schema works pretty good to share validation rules between frontend and backend, but of course you should use the validation engine that is best supported by your tech stack.
Output - How does State change
Handling a command usually results in some changed state in the system. The state change is represented by an Event Card (like Car Added here) and the new/changed state (or the new/changed information) can be visualized by using an Information Card again.
One Feature at a Time
As a last step, we should put our designed action into a Feature Frame and mark the feature as planned.
We put features along an imaginary timeline from left to right just like we do with events in a Big Picture Event Storming. You might have noticed that the Cards within a feature are ordered from top to bottom: UI > Command > Aggregate > Event. This illustrates a specific point in time. An actor makes a decision to change some state in the system based on the information visible on the screen. The decision is followed by a command and now the system can either accept it -> process it -> record an event, or it simply rejects the command so nothing happens.
Each feature should only contain one action (Command > Aggregate > Event) not more. This ensures, that the system as a whole is composed of small, simple building blocks. Connect the features through events and/or steps in the UI. If you strictly follow this rule and learn how to design a system in such a way, you’ll notice after some time that your code is very clean and easy to understand. Refactorings become a no-brainer. You can add new features as reactions to existing events. You can replace or remove features. And you can do this for years without increasing complexity. Sure, your system becomes larger, but zoomed-in to a single feature it will still be as simple as the one we just designed. The Event Map will help you navigate through the system and keep an overview. So make sure that it stays in sync with the implementation.
Now that we have designed a feature on prooph board, it’s time to derive development tasks from it. This highly depends on your workflow and tool being used. We use a Github issue here as an example, but this could also be a User Story in Jira or similar.
Here is our recommended issue template:
|User Story||Derive from Actor + Command + Event(s)|
|Image||Make a screenshot of the feature on proooph board -> paste in issue|
|Link to Event Map||Right click on prooph board feature -> choose “Direct Link” -> paste in issue|
|Sub Tasks||Split design and development work into small chunks|
With some routine you can create issues using this template in less than a minute. Compare this to your normal story writing and task breakdown sessions!
You can also link the prooph board feature with the issue by choosing “Link to Task” from the feature context menu:
The key for any system to be maintainable over a long period of time is composition. A complex system should be composed of simple parts. When designing features around events, you can use a heuristic to keep them simple:
Too much cards in a single feature frame are an alarming signal that the feature is probably too big.
Let’s have a look at the next event on our Event Map: Car Updated. Sounds like a simple CRUD operation, right? But wait, what exactly do we want to update? A Design-Level Event Storming session should give us some insights.
We quickly realize that a car is going to have a lot of information assigned to it. When adding a car, we only focused on the bare minimum information needed. The update command on the other hand should support the full set, even thought it’s a lot.
We know that the Fleet Manager will update a car step by step. Not all information is available right from the beginning for example the licence plate or the equipment list. So does it make sense to have this huge update command? Wouldn’t it be better to split the command into smaller chunks and give the task more structure?
So instead of one big update command, we now have 5 distinct commands for updating different parts of a car. Those commands can be represented as 5 tabs in the UI or a 5-step editing process. In the backend we gain some benefits from this design:
1. Fine-grained events
If we want to add more automation later, the events can act as triggers. Let’s say we want to run an image optimization process whenever an image is assigned to a car.
To add this feature, you don’t need to touch existing code at all. Just implement a listener on
Image Assigned events and call it a day!
2. Simple features
What if we later discover that car equipment can always be changed no matter if the car is booked or not, but basic and technical information are not allowed to change?
You can adjust the business rule for the
Set Equipment command without any risk of introducing a bug in the other commands!
3. Possibility to analyse behavior
We want to make our users happy and help them with our software as much as we can. We do have the feeling that fleet managers spent a significant amount of time maintaining car information. To validate our assumption we can look at the events and see how often a car is updated until it is ready to be published. We can also see what information is updated last or more often than other information. This can give us an idea how to improve the system and provide a metric to measure if our changes improve the situation or make it worse.