Photo by Hans Eiskonen on Unsplash

CQRS, microservices and event-driven architecture with Axon.

George
Technical blog from UNIL engineering teams
7 min readAug 4, 2019

--

Command-Query Responsibility Separation or CQRS is a simple architectural paradigm. It dictates that we should separate our system in two conceptually different parts. There is the command side, also known as the write side, which changes the state of the system via updates, deletes, etc. and the query side or the read side which only queries the state (presenting the information to the user or other modules of the system).

There are several concepts that are often mentioned with relation to CQRS. They are independent concepts but are very helpful indeed when trying to implement a CQRS-based architecture.

Event-driven architecture (or EDA)

In an event-driven architecture components interact asynchronously through well-defined, serializable and immutable messages which at least must be of two kinds. Command messages encapsulate the intention of the user (what needs to be done) and event messages encapsulate the changes to the state usually due to the execution of the commands which triggered them (what has happened).

EDA is all about formalizing causes (commands) and effects (events) in a system as a series of well-known artifacts (messages) being processed by the system at any given moment in time. The greatest advantages of EDA is that components communicating via messages are completely decoupled. They can exist in different runtimes (JVMs, containers, etc.) and they can be active in different points in time, provided a message broker is present to distribute messages asynchronously. Components can easily be tested since the specification of inputs (commands) and outputs (events) is given by design.

Domain-Driven Design (or DDD)

Eric Evans has popularized the idea of Domain Driven Design, which has now become a very familiar idea. In DDD we try to model our software as close as possible to the real-world domain in which our application evolves. Several key concepts standout in this respect. Aggregate is a collection of related entities and value objects specific to a particular bounded context. It is responsible for maintaining an internally coherent state, which can only be manipulated by the aggregate itself (or its constituent entities) in response to the set of well-defined external commands (or events).

It is the notion of domain events, which naturally ties DDD to EDA. Commands are processed by a specific aggregate which performs some domain specific logic involving the entities it controls and which then emits one or several events encapsulating the effects of the change of the state to any interested event subscribers.

Event Sourcing (or ES)

A series of domain events ordered by the emission time can be preserved in append-only event store creating the “source of truth” which can be replayed for each aggregate, effectively recreating its state given some point in time. This technique is known as Event Sourcing. With ES we get a complete audit of our system by design.

While ES is a very powerful technique, it is tricky to implement and to use correctly. This article will focus on an example of building a CQRS-based system without using event sourcing.

Microservices

Principle of bounded context naturally leads to a design pattern where components are build as close as possible to the boundary around each specific domain context. Moreover, these contexts are required to be as independent as possible from each other, and this in terms of runtime, deployment and sharing of data.

This last consideration immediately poses a problem of synchronizing data between services since, depending on the context, several services will need the same data without having possibility to access (and modify) a central shared database. It is here that Microservices and CQRS overlap. The idea is to separate the application into several services (or several deployable modules). For example, there may be one service for each user-facing application (one per bounded context), one service (a gateway) for processing API requests and converting them into appropriate commands send to aggregates, the service handling aggregate processing and persistence (the write side), several projection handling services (the read side). There are, of course, different variations of this approach possible: combining the API processing and aggregate processing in one service, adding a service for long-running business transactions, etc.

Axon framework

In light of the above, it certainly appears that CQRS, EDA and Microservices are well suited, complementary techniques or patterns when it comes to a modern, distributed architecture. At this point, one may be wondering if there are any tools which may help one to get started implementing all these patterns. Axon is just that tool. Axon is a combination of a Java framework and a server, which together help one realize all of the concepts mentioned here. Axon is an elegant solution leveraging Spring framework internally. It allows for creation of applications following the spirit and letter of Domain Driven Design very closely.

By default, an Axon module will connect to the Axon server, which has three main components: command bus, for distributed command processing, query bus, for distributing queries and event store for storing and dispatching events. Aggregates and all event handlers (Sagas, projection handlers), as well as, all query handlers are described via a small set of annotations. Unit testing of aggregates is also provided for.

Here is a schema outlining a CQRS system which may be build with the help of Axon.

Example of CQRS and event-driven architecture realized with Axon

Note that there is no mention of Event Sourcing, even though Axon is specifically designed to support ES out of the box. This is because this post will focus on a use-case which does not use ES.

Example implementation

(Code is available on GitHub)

To illustrate the concept of “location transparency”, central to the philosophy of Axon, the example implementation splits all building blocks of the above architecture into separately deployable modules each with its own database schema. Here are some other architectural decisions which were imposed on the example implementation. As it was already mentioned, event sourcing is not used. This means that all processing groups (aggregates, Sagas) are subscribing and not tracking.

Aggregates are also split into separate modules. The purpose of this decision is to demonstrate that we can split the command side itself, creating separate microservices around bounded contexts, each with a different development and release lifecycle and, possibly, maintained by a different team. Since we are not using event sourcing, aggregates need to persist their state in an aggregate store from where their instances are loaded by Axon prior to command/event handling. This is achieved via Spring Data repositories over separate relational schemas (for convenience single H2 database is used in the example). In addition, Flyway database versioning is used to explicitly record the evolution of each aggregate as the domain requirements are becoming more and more involved.

Application has one case of a long-running business transaction modeled according to Saga pattern.

Each module is implemented as a Spring Boot application. RabbitMQ is used as the message broker with each subscribing processor group (aggregates, Sagas, projection handlers, query handlers) listening to a dedicated queue of events. Docker is used for assembling all the parts of the system.

Here is the overview of the runtime using build-in Axon dashboard. Note that there are two aggregates, one Saga and one projection, each running in its own application instance.

Runtime overview

Write-side model (aggregates) and read-side model (projections).

For this (overly simplified) example we assume that there are two aggregate roots: Person and Address. Usually the address entity is included with the person aggregate but we can imagine the situation where people and addresses need to be treated as different aggregates. So according to the rules of DDD the relation between these two aggregates can be expressed using reference from one aggregate root to the other. Here is the Person aggregate with the reference to the Address aggregate.

And here is the Address aggregate with the reference to the associated Person aggregate.

On the write-side there are only references between aggregate roots. It is up to the projections (on the read-side) to decide the cardinality of the particular relationship when constructing the model for each particular business case. So, for example, a completely denormalized projection can be constructed listing all person to address assignments.

But one can just as easily construct a projection with Person and one-to-many relation to a list of Address, if, for example, a particular ORM mechanism used by a front-end microservice imposes such structure. The important point here is the separation of command-side and query-side models. Former one is imposed by the rules of DDD: references between aggregates, bounded contexts. Later one is crafted specifically for the needs of the user-facing microservice.

Business logic responsible for actual creation of the link between any two instances of Person and Address can be incapsulated in a Saga. In the example application, an assignment of a private address to a person requires a validation action on the part of a user and will be triggered by the Saga via ValidatePrivateAddressCommand sent to a particular Address aggregate if a particular business rule is met.

Projection is then a matter of responding to PrivateAddressValidatedEvent and registering the association.

Note how Saga and Projection components interact using query messages.

Conclusion

In this post we have seen how a CQRS architecture can be complemented with event-driven and Domain Driven design principles while embracing Microservices approach. Key takeaways here are:

  • Axon framework is a major help in adopting CQRS.
  • CQRS can be done without Event Sourcing.
  • Aggregates, query/projection handlers, Sagas and user-facing modules all can be separated into independent modules communicating via messages.
  • Command-side domain model is strictly dictated by DDD principles, query-side domain models may and probably should be different, reflecting the needs of the particular microservices using them.
  • Business rules can be implemented in the aggregate’s command handler (simple case) or in the separate dedicated Saga handler (workflow case) which most likely will query some projection/query handlers for any additional information related to the association between any of the aggregates.

--

--