Let me start by telling you want the Event Store is. The event store is as product that was built to support event sourcing. Event sourcing captures events rather than maintaining a state for a given logical object. Some believe that if you are using Command Query Responsibility Segregation (CQRS) that you must also use event sourcing. This is a myth as Jimmy Bogard explains in his post Busting some CQRS myths.
CQRS = Event Sourcing and vice versa
Event sourcing often fits well with CQRS, as it makes building and updating read stores over time a bit more straightforward in eventually consistent models. Additionally, the aggregates living in the command side for behavior-heavier systems and task-based UIs more obvious.
However, event sourcing is a completely orthogonal concept to CQRS. While they fit well together, doing CQRS does not require event sourcing, and doing event sourcing does not automatically mean we’re doing CQRS.
If you don’t follow Jimmy, I recommend that you do so!
If you use the Event Store or another product like it and are happy with it, you can stop reading now. If you considering using it, or worse, have used it and need a way out; read on.
Benefits of event sourcing
The benefits of using event sourcing, not least being able to see a full history of changes that is highly valuable in sectors like finance and government that require accurate audit logs.
The Event Store is a product that is used to capture the events that happen to a logical object. The idea is that you make changes to the object using events. Those events are then played against the object to change its state. The state of the object is never saved, just the events. In that way, you can rehydrate the object using the events that happened to it to restore the state of the object.
Create the Car
Let’s define a car and then build that car using an event driven model.
- The car will be created by issuing the command “Create Car”
- The command is consumed by the application service, it will create a new “Car Aggregate”
- The aggregate will emit the “Car Created” event
- That event is consumed by the read models as well as the “Event Store”
The car has been created at this point.
Update the Car
Updating the car requires that we rehydrate the Car Aggregate using all the events that we have saved for the car thus far.
- Update Car Color command issued
- The Car Application Service then attempt to rehydrate the Car Aggregate. This is done by reading all the events out of the Event Store and then replaying them in order on the Car Aggregate.
- The Car Application Service then updates the Cars color
- The Car Aggregate will emit a “Color Changed” event that is then stored and consumed by all the subscribers to handle.
This process is repeated for all the changes that we need to make to the car. Depending on how often the object changes you could store hundreds of events for a single logical object. I would have a complete history of everything that happened to the car so I could bring it back to any state in time.
The more events for a given object, the longer it will take the rehydrate the object. You can make this faster using a process called snapshots. This is a process where you create a snapshot of the objects state at a period and save that off as the latest snapshot. Then you would rehydrate from the latest snapshot playing events only from that time to the present.
Cost of Event Sourcing
Event Sourcing requires that you have a larger domain infrastructure. It requires to storage of every event for every object. The Event Store is often immutable, which means you can’t change any of the event contents or the order of the events. Because of this, as a developer, you should make sure you do everything correctly. If you do make a mistake during development, and the event does not have the information you need, then you will need to recreate the entire Event Store in order to correct the problem. The longer you store events, the larger your Event Store will grow. Consuming more and more disc space. The larger the Event Store grows the harder and more time consuming this process will become. A larger the Event Store gets, the longer it takes to objects because the object’s events will be spread out over multiple files.
Why do you need all every event that has happened to an object? If it is for the purposes of maintaining a history there are many other ways to accomplish this. If it is for the purpose of being able to role back, how many times do you need to do this? This also could be accomplished in other ways.
Your customers will hate it. I know ours did. The amount of disc space used by some of our customers stretched from 5 GB to well over 150 GB. The larger the number of events the slower the system got. During an upgrade if we needed to rebuild the read model, it would take hours or days depending on the size of the event store. That full auditing that was purported to be helpful just wasn’t.
Even if having this history of the events pointed us to the problem any solution required rebuilding of the entire Event Store. The real problem to the customer was not the 15th event that had an error in it. It was the fact that the current state had the wrong information in it. So why was I keeping 35 events to build up my state rather than just maintaining the state itself? It was certainly easier to update the current state of an object rather than fix the event and then rebuild everything.
In fact, I could not come up with any valid reason to use event sourcing. So, we stopped using it.
How did we stop using the Event Store and retain our customer base? We replaced an event driven model with a state driven model. We now have the state or the write model in MongoDB database as well as the Read model in MongoDB.
This provided multiple advantages as we did not need the history of every event that ever happened to any object. Not only that but we have secondary read models to populate such as a Reporting Database and Lucene indexes. We took advantage of Microsoft message queuing (MSMQ) in order to send messages to those secondary read models. A message queue processor was created to process the messages published to the queue. We ensured that this queue processor did not have any hard-coded references to our DLLs for the contracts or the projections. Those types are figured out via reflection so that the queue processor would be very flexible and not require constant maintenance going forward.
This offered us enormous flexibility and reduced our maintenance burden going forward. Our customers liked it because we reduced the amount of storage dramatically. We also reduced the number of data related defects due to reduced complexity.
All of this cost the company I was working for at the time a lot of money and ill will with customers. If you are thinking of using event sourcing; I would highly, highly, encourage you not to go down that path. That is why the Event Store met its demise. That is why the Event Store was killed and why it had it coming.