Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
What is Event Sourcing? It’s a way of storing data that is probably very different than what you’re used to. I’ll explain the differences and show ab event sourcing example that should clear up all the mystery around it.
Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.
Developer-level members of my CodeOpinion YouTube channel get access to the full source in this post available in a git repo. Check out the membership for more info.
The vast majority of apps persist current state in a database. Regardless if you’re using a relational database (RDBMS) or a document store (NoSQL), you’re likely used to storing current state.
To illustrate this, here’s a table that represents products.
If we were to have some behavior in our system that is to receive product into the warehouse, we would increment the quantity value. So for example, if we received more quantity for SKU ABC123, we would update quantity value. If we shipped product out of the warehouse, we would decrease the quantity value.
One question to ask from the table above, how did we get to the current state of Quantity of 59 for product ABC123?
Because we only record current state, we have no way to know with absolutely certainty how we got to that number.
Yes, if added logging you could infer with some degree of certainty how you got to a particular state. However, it would not be guaranteed because it actually requires you to write logs in every place that you’re changing state, include outside of your application. This could be incredibly difficult. Ultimately your current state is the point of truth, no matter how you got to that state.
Event Sourcing is a different approach to storing data. Instead of storing the current state, you’re instead going to be storing events. Events represent the state transitions of things that have occurred in your system.
They are facts.
To illustrate the exact same product of SKU ABC123 that had a current state quantity of 59, this is how we would be storing this data using event sourcing.
It’s important to note that events are persisted in what is called an event stream. Event streams are generally per unique aggregate. So in my example, a single product SKU. The above is the stream of events for SKU ABC123.
With the above events, we can see that we Received a quantity of 10. Then we Received 5 more. Followed up by having Shipped out 6. Finally there some extra quantity that was magically found in the warehouse so it was adjusted by another 50. This got us to our current state of 59.
Event Sourcing Implementation
First is to define the events that occur that we want to record. Events are facts that something has occurred. They are generally the result of a state changes from commands. Here’ are the 3 events I’ve defined in our the event stream.
Next comes our aggregate. It is responsible for creating events that will get persisted to the event stream. The aggregate exposes methods to perform commands/actions to our domain. If our business logic passes, then we’ve confirmed that an event has occured.
When an event is added, we call the appropriate Apply() method. These methods are to keep track of the current state within our aggregate so we can perform the relevant business logic (which throws an InvalidDomainException).
For demo purposes, I’m not using an actual database to store our event stream, but rather just in-memory dictionary and list to illustrate. The important part is your repository is responsible two things, building your aggregate and saving the events from your aggregate.
When you want to build/get a WarehouseProduct from the Repository, it will get the events from the event stream, then call ApplyEvent() for each existing event. This is replaying all the events in the aggregate to get back to current state.
Then after you have called commands like ShipProduct/ReceiveProduct/AdjustInventory, the new events will get appended to the event stream from the Repositories Save() method.
The current state used in the aggregate is called a projection. It represents the current state of the event stream. I’ve covered more about projections and how they are used in UI and reporting to build many different models from your event stream.
I’ve created a simple console application that has all the code above in an interactive way. Developer-level members of my CodeOpinion YouTube channel get access to the full source and demo available in a git repo. Check out the membership for more info.