What’s an event in the first place? What to store in an event store? Sure, technically that’s easy; I mean what to take as a signal for “I better record this fact”? Because there is no shortage of facts. So much happening all the time while an application is serving a request.
Whenever some value is calculate a fact is created. Every decision adds a fact “to the universe”. But it would be insane and counterproductive to record all that. Only carefully selected events should be recorded. But which ones? I have been struggling with the answer for quite a while.
Over time some patterns and heuristics have emerged, though. Let me share them with you.
External facts
The obvious event is one related to something outside the application; something happened in the real world and the application is triggered to work with that. External events are internalized, they are recorded in the application’s event stream because they change the application’s state.
Examples are:
a task gets created
a task gets checked-off
money is withdrawn from a bank account
a product is put into a shopping card
a student enrolled in a university course
a student got graded for her performance in a university course
a car enters a parking lot
All these events in the real world should of course be recorded as events in the respective software systems. And maybe they even should be correlated with the command triggering this change.
Impact on the environment
An application does not just receive notifications about changes to the external world, it also acts on its environment. Whenever such an intervention is not idempotent or expensive I would record it. Examples for this are:
after sign-up an email was sent to request a confirmation
a task became overdue and the user got notified
the schema of database was updated
If such side-effects would not be recorded a re-interpretation of events might trigger them again and cause damage or confusion.
This category of events also covers events triggerin obligations. If for example a certain pattern of withdrawls is deemed suspicious and requires reporting, then that should be recorded.
Major lifecycle changes
Related events might be part of a lifecycle of a scope. An example of that would be a game with a starting event and an ending event. Game rules would determine when it’s time to transition the started/running game to a finished status.
Some other examples:
a contract expires
processing of data is cancelled due to an error
If a lifecycle change is reported by the user, that’s an external fact (e.g. a game is started). This additional event category is concerned with changes determined by internal rules: a game is finished automatically when, for example, a player made a certain move. The move is the external fact. But in addition the end of the game resulting maybe from a certain configuration of tokens on a game board is recorded.
Keeps interpretation stable
Events are unchangeable, logic can change at any time. Logic interprets events. Example: the rules of a game determine when which player wins. As long as the rules don’t change, reading the events multiple times won’t change the result.
But what if the rules change? Maybe that could lead to a different winner? If the winner of a game is beyond re-interpretation of events, then the decision should be recorded when it’s first reached.
Another example: Today a discount may apply if the total price of the items in a shopping cart reaches a certain limit. Applying the discount could be a matter of a query. But if the discount rules change in the future, the total price might be reported differently. To prevent that, granting the discount should be recorded.
This might be a narrow line to walk: the event stream should stay open for re-interpretation as much as possible, but at the same time it should not promote distortions/misunderstandings.
This for me maybe was the hardest category of events. I tended to record too many because I wanted to fix “everything”. It put a burden on commands, increased the number of events to juggle, and narrowed too much future re-interpretations.
An example of that would be recording strikes and spares in a bowling game in addition to the raw throws with the pins knocked down. Yes, a decision has to be made if a frame is a strike or spare, but that’s transient. It’s only of temporal value while calculating the current score.
Just because something looks like an important decision does not mean it has to be recorded. If it is cheap and can be made again and again and again, then don’t create an event for it.
Less is more in event sourcing. Keep the gates wide open for new perspectives on events.
Documentation of failure
The user has a request for changing the application state, but that fails. The external fact cannot be recorded as expected. An error response is returned from the command. That’s it?
I suggest to look closer. Sometimes nothing more needs to be done. But sometimes a failure is an event worth recording. Here is an example: A user attempts a login, but fails. She tries again and again. Is that noteworthy? Yes, because it might not be a case of a forgotten password, but an attack. In this case it’s warranted to record failed attempts and maybe after the third within a certain period send a notification to the email address associated with the account.
Or an application tries to connect to a service. If that’s not available some “silent” retries might be in ordern, but after a while they should be stopped, the failure recorded, and the user notified.
If you encounter a failure or violations of rules or need to reject requests for any reason look closely and think about if it’s helpful to record it. Later these events might provide insights for improving your system.
Conclusion
Not everything happening in your application is a record-worthy event. Of course. But there are events beyond the obvious external facts passed to your application that warrant recording. Look closely!
Design is not gone from AQ software cell applications. But it’s different design than in your usual CRUD application.
No big single, central, universal object model needs to be designed. Instead it’s tiny event data structures (payloads) and local, readily comprehensible context models for slices.
Both kinds of data structures are focused on what’s known in the moment, though. There is no (or very little) speculation about future developments.
To determine which events to record look at the current requirements (and past events). Go through the above criteria one by one and see which one resonates with you while working on an increment.
Maybe there are more criteria? Please let me know if you can think of any.
Of course you can expect the answer not always to be easy. You might get it wrong. But simply going by your gut feeling is not a recipe for success either. Some “hard and fast rules” (at least for the start) are useful. Being uncertain which events to create should not be an excuse for not getting into event sourcing.
In many introductions to event sourcing introductions the events written are obvious/trivial because they are external facts. Don’t get fooled by that! There are more events worth recording. I hope my checklist helps you spotting them.
And if in serious doubt… decide in favour of yet another event. Not recording means losing forgetting a fact. Recording more than in the end gets used just wastes a bit of storage and requires some weeding out later on. Ignoring events in the future might be a nuisance, but is easily done. Avoid frustration about having ignored to record something important.
That said: Be ready to experiment. It takes some time to getting used to recording events and projecting them into a multitude of small data models. It’s a paradigm shift that goes deep!




