Event Sourcing and CQRS are quite simple concepts. But they are often made complex to understand because of overly complex implementations. Implementing Event Sourcing with CQRS in a simple application has its benefits, and it can be done without compromising the simplicity and maintainability of the application. And in this article, let’s see a practical implementation of Event Sourcing and CQRS using MongoDB Views.
This article is a part of my series on building a microservice architecture with Node.js. You can find the rest of the articles in this series below:
- Bunyan JSON Logs with Fluentd and Graylog
- Error Management in Node.js Applications
- Implementing Event Sourcing and CQRS pattern with MongoDB (This article)
- Canary Health Check Endpoints (Coming Soon)
- Writing MongoDB Database Migrations with Node.js (Coming Soon)
Event Sourcing is a pattern where every action is considered as an immutable event. A series of events occurred in order, determines the final state of a particular object. The advantage of the event sourcing is, it allows tracking the history of a particular object. Also, it allows re-creating a particular object’s state at a given point on the timeline.
During the rest of the article, we’ll be implementing a Jira-like simple Issue tracking system using event sourcing and CQRS. Our goal is to implement this in a way that a user can view a ticket and see the history of changes made on that particular ticket. Let’s move forward to the terminology.
Command Model (Event)
In our example, an event/command is a document that contains details about a single operation performed on a ticket. We call this the Command model in the CQRS context. An event contains the following information:
type—Whether the action is a
tid— Ticket ID which the action performed on
data— Action payload (changes made to the ticket)
author— The user who performed the action
timestamp— When the action occurred
Let’s assume that the following events occurred in order. We will store them in an event collection (e.g,
ticketevents) on our MongoDB database.
Users of our issue tracking system are not interested in individual events. They need to see the current state of the ticket as a single object. This is what we call the Query model. In our application, the users are interested in the following view which represents the final state of the ticket after the series of events.
As we can see, the Command Model and the Query model are quite different which we call the Command Query Responsibility Segregation (CQRS). Implementing this is quite straightforward with a MongoDB view. We can create the following MongoDB view
tickets on our event collection
ticketevents in order to derive the above output from the events.
The above view is created using a MongoDB aggregation pipeline which sequentially performs the following operations to derive the final output.
$sort: Sort events in the ascending order of
$group: Group events by
tid, and generate
$replaceRoot: Build the final output
$project: Remove unwanted properties/values
An alternative way to implement the above application is by aggregating events within the application which will be an expensive operation for the application as well as introduces additional complexity into the application code (e.g, building
history field, aggregating
comments into an array). By implementing this aggregation as a database view, we can offload complexity to the database and keep the application code simple.
MongoDB views support almost all of the different read operations (with a few minor exceptions), therefore you can also even custom projections with
find queries similar to how you query from an actual collection.
One of the common features of Event Sourcing is the difference in how data is written and read. This is the primary reason why CQRS is usually bundled with Event Sourcing. Implementing this pattern in such a simple way using MongoDB views, helps us to achieve all the benefits of Event Sourcing and CQRS without compromising the simplicity and maintainability of the application.