Event Sourcing and CQRS

Event Sourcing and CQRS

·

4 min read

In a previous post, I described the CQRS pattern (marc-architect.hashnode.dev/cqrs-pattern).

Now, I'm going to write about event-sourcing and how it can fit well with CQRS.

Event-sourcing purpose is to persist an object as the sum of all its states.

Instead of storing an object state at a point in time, we will store all the object states change events and deduce the current state as the sum of all the events:

event-sourcing.drawio.png

  • On the left, you always have the last state of the object.
  • On the right, you need to compute all the states of the object, from the first to the last, to get the current state.

This is event-sourcing and now I'm going to explain why it fits well with CQRS.

I remind that CQRS pattern purpose is to separate (segregate) the reads from the create/update/delete operations to the object (aka. aggregate).

  • The create/update/delete operations to the object will follow the event-sourcing pattern just described above.
  • The reads operations will be directed to a view built upon the events and maintaining the object current state (or any other previous state btw).

See the schema below:

eventsourcing-cqrs.png

The blue squares represent the aggregate creation steps:

#Aggregate Creation stepsNotes
1The command handler receives CreateObject command
2The command handler requests to the repository if the aggregate instance exists into the repositoryAn aggregate is a cluster of domain objects which are considered as one unit with regard to data changes. A transaction within an aggregate must remain atomic.
3The domain repository checks if the aggregate instance is in the cachenot in cache
4The domain repository checks if the aggregate instance is in the event storenothing in Event Store
5The command handler trigger the invariant verificationAggregate enforces its own data consistency/integrity using invariants. In case of failure, throw back an exception indicating the business problem.
6The command handler publish ObjectCreated event on the Event busThis is an internal event bus.
7The domain handler receives the ObjectCreated event from the Event bus
8The domain handler creates the aggregate instance from the aggregate root
9The ObjectCreated event is persisted into the Event store
10The cache is updated with the created aggregate instance
11The view handlers receives the ObjectCreated event
12The view handlers update their views

The green squares represent the aggregate update steps:

#Aggregate Update stepsNotes
1The command handler receives UpdateObject command
2The command handler requests to the repository to return the aggregate instance
3The repository check if the aggregate instance exists into the cacheif yes, go to step 6
4The repository triggers the rehydration processcompute the current state of the aggregate instance from the Event Store
5The repository puts the rehydrated aggregate instance into the cache
6The aggregate instance is returned to command handler for invariant verificationthen pursue since step 5 from Aggregate Creation steps

Benefits:

  • Reliably publish domain events: events are reliably published whenever the aggregate state change (in event-sourcing, objects are built upon events versus events are generated by objects in a traditional way)
  • Provide an audit log and activity tracing log: all the events can store the identity for audit - they can be consumed by a specific handler to publish them (or part of them) on a message broker for external use
  • Preserves the history of the aggregate

Drawbacks:

  • Different programming model
  • Performance: when a domain object is built upon a huge number of events, there may be some performance issue - solution is to use snapshots (this issue is not present if domain objects are built on relatively small number of events)
  • Evolving events: schema of events may change over time - so the already stored events do not conform to the current schema any more - solution is to upgrade events to the latest version when they are loaded from the event store (upscaling)
  • Deleting data: you cannot delete data - solution is to do a soft-delete (emit a Deleted event)
  • If using a framework, this adds the framework dependency to the service's domain layer

Takeaway:

This pattern is suitable when there is a strong need of activity tracing (at domain object level) and a strong need of domain objects states investigations.

It is also suitable for domain objects that may have various state changes through their life-time.