My Journey into CQRS and Event Sourcing

Using Elixir — Part 1

Rodrigo Botti
Nexa Digital
12 min readSep 11, 2019

--

Three small flasks sealed with corks. From left to right: blue, red and yellow dye are being slowly mixed in each one.
bottles-potions-ink-dye-magic by JalynBryce

Note: this is part of “My Journey into CQRS and Event Sourcing” series. If you like what read here, please consider reading the next ones.
Part 2 >

Introduction

A while ago I decided to start learning more about CQRS and Event Sourcing. Those were both architectural patterns that have always fascinated me, that have always seemed like a good fit for an evolving application/system architecture specially when it comes to a microservices based one. Unfortunately I've never had the chance to work in an application/system which applied both these patterns.

Since I know that I learn something a lot better (specially when it comes to software development) when I actually practice it — code it, implement it, deploy it, etc — I decided I should give those patterns a spin by coding something that actually uses them.

This is the telling of the start of my journey into learning these patterns by building an application — or set of applications/services — that apply them.

Note: I'll be using Elixir code snippets to explain the concepts this article intends to present. The application built using these patterns is also written in Elixir. I will assume you have some Elixir knowledge — syntax, core functions, runtime.

The Theory

First let's start by defining these patterns in a very loose way. I do encourage you to read the more extensive definitions of them — some will be linked at the end.

CQRS

It's an acronym that stands for Command Query Responsibility Segregation.

"At its heart is the notion that you can use a different model to update information than the model you use to read information" — Martin Fowler

Basically we split our read — query — model from our write — command — model. We can represent that in a diagram — just replace UI component by a generic client:

CQRS Diagram by Martin Fowler

Event Sourcing

"Event Sourcing ensures that all changes to application state are stored as a sequence of events. Not just can we query these events, we can also use the event log to reconstruct past states, and as a foundation to automatically adjust the state to cope with retroactive changes." — Martin Fowler

In a typical CRUD application we mutate persistent state stored in a database — e.g. table records in a relational database. If we have an User entity and we want to change it's name, we store the same User but with its name changed.

With event sourcing rather than mutating persistent state, we actually store a sequence of events in an append-only event log called event store.

For the example above, we would instead append an event of UserNameChanged(user_id, new_name) for instance.

Note: There's more to event sourcing than just event logging and the event store. I suggest reading more on it from the references provided.

Putting it together

There are many ways to do Event Sourcing, but one pattern that is commonly used is CQRS.

CQRS Diagram from DDD Building Blocks Dictionary

On the command/write side we dispatch commands to the domain model and respond immediately. By handling the command — business logic — we emit events onto the event store, which are then fed back onto the domain model for potential event cascading but also to event handlers. The handlers then modify the read model for further querying by the client.

"At first this might seem unintuitive or verbose, but it allows for exciting features. Splitting read and write heavy operations in the system allows for separate tuning of the components that handle them — optimizing software for writing yields tremendous performance. But more profoundly, CQRS allows one to adapt to those data model changes so prevalent in fast-moving MVC codebases much more aptly. By constructing data after the event is stored, we’re free to change our interpretation of it in the future or add new features, and go back to past events and apply those learnings to build new data structures in the read model with retroactive effects." — Bruno Antunes

The Application

First we need a problem to solve, one that would benefit from applying these patterns.
Since I work for a health-tech company I decided to tackle a problem which we'll eventually need to address: Consent Management.
Why? With event sourcing we store all events instead of mutating a persistent state so we get auditing "for free". We can also handle consent events to trigger user-facing side effects such as push notifying users when something happens or use event streaming to integrate with other services asynchronously. Also, something not related to the domain, we can evolve our read model according to evolving business requirements — if necessary we can replay all events and reconstruct our read models for new use cases.

Note: The application is implemented using Commanded. Commanded is an awesome Elixir library that helps abstract away some of the plumbing necessary when building an application of this kind. We'll be blazing through some of it's concepts and building blocks but be sure check out the full documentation.

Requirements

We need a service that allows for health care professionalsdoctors and nurses for now — and patients to ask a patient for consent i.e. permission to view his/hers health records — exams, consultations, conditions and ER visits for now. The patient should be able to grant consent — give permission — revoke a granted consent — remove permission — and view/list all consents he/she has granted.
There should also be a way to easily know whether or not a health care professional has consent over a patient's health records.

From this we can define some aspects of our service:

  • Actors: patients and health care professionals
  • Write Side (Command) API: ask for consent, grant consent, revoke consent
  • Read Side (Query) API: list patient's granted consents, check whether or not a professional has consent
  • Business Invariants: * A professional cannot ask for a consent which was already granted; * A patient cannot grant a consent which was already granted; * A patient cannot revoke a consent which has already been revoked or was not granted; * Consents can be asked by other patients; consents can be granted to other patients; * Professionals can only be doctors and nurses; * Permissions can only be given/asked/revoked for health records of type exams, consultations, conditions and ER visits

With the higher level api of our application defined and the requirements we can derive:

  • Commands: AskConsent, GrantConsent, RevokeConsent — Referencing a patient id, the professional's id, the professional's type (e.g. doctor or nurse), the health record type being targeted.
  • Events: ConsentAsked, ConsentGranted, ConsentRevoked — Also referencing a patient id, the professional’s id, the professional’s type, the health record type.
  • Aggregates: Patient — Identified by the patient id; its state should have a data structure capable of rapidly checking for the existence of granted consents.
  • Projections: PatientConsent Entity — references the patient's id, the professional’s id, the professional’s type and has a list of health record types to which permission has been granted.
Event Sourcing With Elixir — Main entities in Commanded Library by Bruno Antunes

Now let's dive into some implementation.

Commands

We will define our commands as Elixir structs. We'll also have one module per command:

Events

We'll also define our events as structs and have one module per event — remember to have the event's name in past tense:

Notice how we derive the Jason.Encoder protocol. That way commanded is able to serialize our events as JSON encoded binaries prior to saving them to the Event Store.

Aggregate

Our Patient aggregate is an Elixir Module that:

  • Defines the aggregate state — a struct
  • Has two kinds of functions

For our case:

  • the state will be a map: key is a tuple of {actor_type, actor_id}, value is the list of permissions granted. That way it is easy to enforce our business invariants
  • command functions will pattern match on the struct type of the aggregate and of the command, enforce the invariants and return an event
  • state mutators will pattern match on the struct type of the aggregate and return the updated state reflecting the event application

Note: both the state mutator and command functions take the current state as parameters. This stateful behaviour is possible because Commanded spawns a GenServer per aggregate instance.

Command Router

The command router will be an Elixir module that adopts the behaviour of a Commanded.Commands.Router. Here's where we define how to forward the commands to the appropriate aggregates by defining which commands are dispatched to which aggregate — and possibly a command handler — and aggregate identity:

Now when we dispatch a command through the router it will forward it to the appropriate patient aggregate identified by the patient's id.

Note: It is possible to dispatch the command to a Command Handler — which receives the aggregate and the command in its handle/2 function — to validate, authorize or enrich command data prior to calling the aggregate. In our case we are forwarding the command directly to the aggregate.

Event Store

The specialized data store that stores the events. Commanded ships with adapters for two of them: Commanded author’s own EventStore, open source and based on PostgreSQL — the one we're gonna use — , and Greg Young’s Event Store, also open source and with optional paid support.

To configure the eventstore:

This will configure commanded to use PostgreSQL as the Event Store and also configures the connection to the database.

Api — Write/Command

Now that we have the command side implemented we can start implementing our high level api and it is as simple as having functions that build commands from their parameters and dispatch it using our router:

Projector

A projector is a specialized Event Handler, it's a module that listens for events and builds/changes the read model. We're gonna use another commanded library — commanded-ecto-projections — that simplifies projector implementation with Ecto. With this library we're going to build our read model using a SQL database supported by Ecto — Postgres.

Our projector will listen for events and create/update our PatientConsent entity — patient_consent table.

First let's define our Ecto Schema for our read model:

Now we have to configure the Ecto Repo for our read model:

Note: Ecto is an awesome library but explaining it's details is out of the scope of this article. Please refer to the official documentation.

Now we can configure and implement our projector.
The projector uses the project/3 macro from Commanded.Projections.Ecto. We just have to pattern match to our domain events and create/update our read model using the Ecto.Multi passed to the callback function:

Api — Read/Query

Now that we have our read model ready we can implement our queries on our high level api:

Running the Application

Let's run our application in the terminal with the IEx interactive shell— you'll need to have Elixir and git installed.

Now we can interact with our application in the interactive shell. We'll do that by using the high level api — Consent module:

As we can see, dispatching the commands enforces the business invariants and returns immediately. When we query the read model we can see the handlers — projectors — managed to modify the read model allowing us to query it.

Conclusion

By building this application I managed to accomplish learning about some of the pattern's concepts in practice and clear some misconceptions I had about some elements of the architecture.

The most important thing for me was that I was able to understand in practice the responsibilities of each component of the architecture and how they all connect together.

Summarizing what we learned so far:

  • How to combine CQRS and Event Sourcing
  • Components of the architecture: Command Handlers, Aggregates, Event Handlers, Event Store
  • How the components connect together to form an application
  • Using Elixir with Commanded for implementing the architectural patterns

References and Comments

--

--