The Need for Event Sourcing
Revisiting the concept after a minor incident.
A colleague just triggered me to revisit what the heck is event sourcing when he mentioned something about building aggregates in the context of microservices.
Unwanted Application State
I first discovered event sourcing from this article written by Martin Fowler.
Back then, I couldn’t grasp the concept completely, but the experience is always the best teacher. In November, our team experienced a minor incident in which all of the data in one table were updated into the wrong state by something we could not identify.
At that time, we had several proposed solutions. First, we could try looking at the transaction log and tinkering with it. But it turned out to be cumbersome. We had that data log, yet it was not making things easier.
The second alternative was to restore the previous backed-up database. Luckily, we had one backup recorded a few hours before the incident happened, the closest time frame we could recover. Still, it was not without risk. Restoring the previous states with backup data means there is potentially data missing. Yet, it was ineffective since we had to override all unnecessary/same states (data).
Lesson learned, and I knew we had to do something in the future.
Event sourcing is a fitting solution for this kind of problem.
With it, every alteration to the state of an application is recorded in an event object. So, when data within the same context goes wrong right after executing some actions, we can always replay those actions and expect the correct results. If the root cause is a not-working workflow, like what we suspect in our case, we can check each part of the step and make sure it works. Then, we can replay it from the moment it goes wrong.
The solution can also work in similar cases.
If someday, for example, the stakeholders want every new member to receive a bonus, and it should be retroactive, say, ten days back, we could refactor the code and replay the registration events from ten days ago.
The replay could only run effectively if we routinely log every event. An event is a command triggered by an action –mostly by users –and by which the application state would change. It’s a familiar thing, isn’t it?
The fundamental idea of Event Sourcing is that of ensuring every change to the state of an application is captured in an event object, and that these event objects are themselves stored in the sequence they were applied for the same lifetime as the application state itself.
What is an event store, anyway?
So we have a thing called the application state and a thing called an event store. An event store is where we keep all event objects in order. But, the event object should be stored in different storage than the application state.
Why, you may ask?
Because, if something terrible happens to the application’s database, the event store would also be affected if it resides there. A compromised event store would become useless. So, it’s partly different from the usual audit trail, as it turns out to be something like a backup.
If we have an event store, we could always rebuild the application state as much as we like. Even for different application instances in different environments, but always in the same state. For example, you want the same application state in the staging environment for the production environment.
Wait. Couldn’t we just use a backup database to do that?
Yes. But what if you want an application state from a specific time? Maybe from three days ago before 2 PM, or before an account named “Alev” made his first opening.
Replaying or rebuilding the states is possible. Furthermore, we can refactor the code (if there is a change in logic) and then rebuild it from and/or to any step we want. And this kind of operation will not affect other non-related states.
So in regards to retroactive changes, we can always debug the application using past events to see if the application states will come up as expected.
How or Why A State Got Here and There
While the traditional system only displays the current state, the sequence of events shows us the how or why of a particular state. It means that having event sourcing implemented is having more “why” and less “what”.
While traditional CRUD mostly only shows:
1. Who is Bambang’s line manager?
2. How many points did Fahri get today?
Event Sourcing allows you to see:
1. How did Hari become a line manager?
2. Why does Fahri have the most average point in the last three months?
Hold on, wouldn’t the event store be extremely large? Yes.
What about querying it? Could you imagine that?
This is why we cannot just use the old-school database to store the events. We need a more advanced strategy to not just depend on the kind of database we are using to be efficient and more effective.
With the event store, we can do amazing things like:
- Rebuild: from the start or any step to any step or all steps
- Undo: to any steps
- Retroactive changes: refactor the code and rebuild
I researched many YouTube videos and tutorials about event sourcing and finally tried to implement the concept of event sourcing in a very simple way. The codes I wrote were as simple as just proving that replaying events, as well as its variant function like undoing some events is feasible.
I made these simple base classes and interfaces so that I can experiment with Account states.
Disclaimer: this is not a world-class solution nor a practical pattern, only to simulate the very basic idea of event sourcing.
So for the Account context, I will implement and extend those interface and abstract classes.
The event storing flow is as follows:
- AccountCommand will take a command (open, transfer money, or close account).
- AccountEventProcessor then will handle the command, convert it to an event, and pass it to AccountEventStore.
- AccountEventStore will then store the new event.
There is an AccountSubscriber, pretending to be a subscriber of AccountEventStore. This subscriber will handle the coming event and call the AccountRepo. AccountRepo is used to do the usual CRUD. AccountEventStore is responsible for preserving a new event in a separate database.
The fun begins at AccountEventProcessor.
Here we go.
First, prepare the objects:
Then enter the commands:
And play around with it:
The undo process will undo 3 steps back, where Alev’s balance is still 5000 and Oki’s account is still active.
The rebuild process will replay 5 steps from the beginning, so that the final states of Oki, Idam, and Alev’s balances are respectively 800, 1500, and 5700.
That’s it. I hope everyone could grasp the idea.
But there are some lingering questions:
- What if there is a related state but it has a different context?
- If we already closed an account, while in another event, its profile was updated before, when we decide to undo the “close” event, how do we seamlessly undo the profile update event?
I then realized that while event sourcing seems beneficial, it is easier said than done. Unfortunately, the stack we are using right now does not seem perfectly match the requirement.
There are still a lot of holes I need to dig more in-depth. But I see that it is promising.