DDD & co., part 7: CQRS

tl;dr: The design pattern CQRS describes the separation of the write side from the read side of an application. CQRS and DDD do not necessarily belong together, but complement each other excellently, which is why their combination is often used in practice.

Aggregates ensure the consistency of the data in a dedicated area of the application. They process commands and create domain events that can be persisted in an event store. Replays and snapshots allow quick access to all events of an aggregate.

This works, but focuses on those processes that change the internal state of the application. However, this does not apply to every access: If only data is read by executing queries, for example, no state is changed.

Working with an event store also has some problems. For example, you can only access domain events using their aggregate ID. A list of all aggregates is not offered by default.

In many cases, however, this is essential - for example, to display a list of open or completed todos in the TodoMVC application. It can therefore be summarized that the concepts presented so far are well suited to writing data, but less suitable for reading.

DDD meets CQS

This corresponds to the design pattern CQS (Command Query Separation) invented by Bertrand Meijer. It defines that each function of an object should be designed either as a command or as a query.

The difference between commands and queries is that commands change the state of an object and do not return data, whereas queries behave in exactly the opposite way: They are used to query the state of an object, but are not allowed to change it.

The concepts of DDD presented so far make it clear that aggregates are concerned with commands, but not with queries. This is not surprising, since queries have no influence on the transactional boundaries of the application.

For the reasons mentioned above, however, an event store is hardly suitable for queries. Although numerous questions can be answered on the basis of the stored domain events, this requires a complex and time-consuming analysis. This may be acceptable for reports, but not for day-to-day operation, where data must be available as efficiently as possible.

CQS on application level

The solution is to transfer the design pattern CQS, which refers to individual objects, to the entire application architecture. An application then no longer consists of a single system that is equally concerned with writing and reading data. Instead, it is broken down into two subsystems, each specializing in one of the two processes.

This approach is called CQRS (Command Query Responsibility Segregation). As with event sourcing, CQRS and DDD can be used independently of each other, but they fit together perfectly.

The idea is to add a second database besides the event store, which is specifically designed for reading. Since the aggregates in conjunction with the event store already ensure consistency, the read database does not necessarily have to be normalized. Actually this means that it can be denormalized if it makes sense for efficient query execution.

Dedicated read tables

For example, there might be a dedicated table that contains all the open todos of the TodoMVC application. The associated query is reduced to the following simple SQL statement:

SELECT * FROM noted_todos

The same applies to all conceivable queries. They should not be more complicated than a simple SELECT, supplemented at most by a WHERE or ORDER BY. In particular, this method eliminates the need to execute queries across several tables using JOIN.

This leads to extremely efficient reading processes. Since the read database is not responsible for ensuring consistency, it can be replicated without any problems. If an instance can no longer achieve the desired reading performance, you simply add more instances. This way reading can be scaled practically arbitrarily.

Synchronize writing and reading

All this brings up the question of how to synchronize the application's writing and reading. This is usually done using a separate process that reacts to domain events and updates the read tables according to the desired interpretation.

Whether an event in a read table is processed as an INSERT, UPDATE, or DELETE depends on the respective semantics. In the end, CRUD actually comes back into play here, but in a defused form, since it only serves to update the read tables. The consistency checks take place earlier using domain events and aggregates.

It is obvious that this procedure has side effects. After all, a certain (although short) time elapses between writing the domain events to the event store and updating the read tables. The next episode will focus on the consequences of this.

Twitter Facebook LinkedIn

Golo Roden

Founder, CTO, and managing partner

Since we want to deliver elegant yet simple solutions of high quality for complex problems, we care about details with love: things are done when they are done, and we give them the time they need to mature.