EventSourcing is a straightforward pattern to both use and understand. However, why do we encounter numerous strong opinions, challenges, or even failures?
The primary reason is usually due to confusion.
In this article, I will attempt to elaborate on my thoughts by demystifying the definitions in order to mitigate this confusion.
The Definition
Thanks to the community's advocacy, and particularly the efforts of Oskar Dudycz, we can observe a decrease in the level of confusion between EventSourcing and EventStreaming or EventDriven.
Sometimes, it's also easier to grasp a pattern when we initially learn when it should not be applied.
In summary, Mathias Verraes's definition is exceptionally clear and concise:
A system is EventSourced when:
- the single source of truth is a persisted history of the system's events;
- and that history is taken into account for enforcing constraints on new events.
If the EventSourcing definition is well-understood, we are already halfway there. However, to reach that point, it's also necessary to understand what an EventStore is in practice.
Event stores are essentially key-value databases with features such as atomic writes, optimistic concurrency or idempotency. The key usually consists of a name and an ID, while the value is an ordered list of events. This key-value pairing is commonly referred to as a stream aka history.
Stream's lifecycle
Keep it short! Which means short enough to be able to read the entire stream without any performance issues.
To achieve this, prior modeling is crucial.
Event Modeling is an excellent method to discover and describe a system using an example of how information has changed within them over time. During the modeling we can define boundaries and independent streams therefore how to keep them short.
It's always less expensive to refine or redo a modeling rather than realizing it was a mistake after the implementation or the release.
If this is not possible, my two cents is to avoid EventSourcing altogether! It's worthless.
Big streams are typically anti-patterns and are disguised as CRUD operations, cumulative data or command sourcing.
For instance auditing is one of the outcomes of EventSourcing, but we don't do EventSourcing solely for the purpose of auditing. So it shouldn't be used for updating entities info. There are other suitable patterns for this.
Snapshots technique is too complex as well and must be at the very bottom of the decision tree.
State VS Projections
The final boss! Which is the confusion between the state and projections. if we aren't familiar with earlier definitions, the failure might occur swiftly. But this one can heart slowly. It's hidden behind many tricks and fixes.
However it's an easy boss to fight because it's again a matter of definition.
State
The state is private. It’s built only in-memory each time we load the history. That’s all!
As said before It's used to enforce constraints on new events.
Jérémie Chassaing's explanation is quite straightforward:
-
Reduce the history by evolving each event
state -> event -> state
, to get the current state. -
Decide
command -> current state -> events
, to get the command's behavior as new facts. - Append new events to the history.
Projections
On the other hand, projections are public and it's very important to point out that there could be many of them.
Every time new events are appended to history, projections are triggered right after.
Like the state the history is replayed, but this time, it's in order to know how to update the read model.
What's interesting with projections and so read models, is that what is projected is concise and simple and doesn't contain all the internal state and not necessary data. They are optimized for the view and it's cheap to have many of them. No need to have a complex relational databases.
Common misuses are to project the state, to project from one event and not from the history, to not optimize the read models for views, or to make a canonical or monolithic model.
Conclusion
EventSourcing is a straightforward pattern to both use and understand. However, it's crucial to grasp the definitions, regardless of implementation details.
Top comments (3)
Quick question regarding the reduce state -> event -> state. How do you build the input state? Is it the responsibility of the caller or the reduce function can rebuilt the state in memory from the history?
I doesn't matter I would say. What is important is that the Decider module must contain two functions which are
decide
andevolve
.Then we can reduce the history with the
evolve
function and the initial state, in order to build the latest state.In this TypeScript example it's outside.
And in this C# example it's inside.
Thanks a lot :) I will dig into the examples!