Event Sourcing

Capture all state changes as a sequence of events

Published on Sunday, October 25, 2020

The traditional way of developing a managing entity is to save its current state only. This has to do a lot with hardware limitations we used to have and the basic concept of relational databases that served us very well. However, tables do not represent events that occurred, only the current state of the system. Sometimes it is enough, but not always. What happens if we need more?

Saving the current state only is radically different from many other areas, like accounting, where the current state does not mean as much because you have to prove how you got there. Your bank statement is not just the ending balance (although it is the most important number), but all transactions, including deposits, charges, withdrawals, as well as starting and ending balance.

Event sourcing is very different from the "classic approach" it is an event-driven approach, where the entity is persisted by storing a sequence of state-changing events. When an object is changed, a new event is stored at the end of the sequence of events. To get to the entity's current state, you have to reconstruct it by replaying all events.

The simplest entity that we could track would be just a bool value - let's say - Turn On Light, Turn Of Light. Every time the user "changes the state of the light," a new event is added to the event store – "a database of all events." To get to the current state, we would have to replay all the state changes (or get the latest state change because you're only tracking a single value).

A more complex example would be classic CRUD (CRUD without READ) with a list of entities. Users can Create Entity, Update Entity or Delete entity, and each action can be tracked by event sourcing and replayed to get to the current state. Depending on the needs, we can use similar out of box solutions like SQL Temporal Tables. However, Event Sourcing has many other benefits.

Event sourcing begins to shine with more complex examples when you have to consider additional requirements like:

  • Event-driven Architecture - When you have to implement the event-driven architecture, event sourcing makes it possible to publish events whenever the state changes. Tasks do not need to know what triggered that event, only what type of event it is and the event data.
  • Object-relation impedance mismatch problem - It also solves the object-relation impedance mismatch problem because it stores events and not domain objects. It is much easier to understand the concept described in the event than to understand complex database tables.
  • Audit Logging - Event sourcing "is" logging. There is no need for additional implementations, especially not after most of the functionality is completed, and you don't want to make extensive changes.
  • Temporal Queries - It is possible to determine the state of the entity at any point in time.
  • Immutability - Events are immutable and should be stored as append-only operation.

Snapshot

For many cases, with many events, it would be very impractical and expensive to replay everything from the beginning every time to get to the current state of the object. We can create a Snapshot - a type of Materialized View Pattern implementation that combines all events up to a certain point in time (or up to a particular event). We can query that snapshot, replay the missing fraction of the events, and get the current object's current state.