My Journey in Learning Domain-Driven-Design part3 (CQRS)
In the previous part, we went through DDD Layers and Hexagonal Architecture. in this part we will learn about the CQRS pattern and why to use it.
CQRS stands for “Command Query Responsibility Segregation” which we can separate write models(Commands) from the reading model(Query).
Why using CQRS?
some times It can be difficult to query from Repositories all the data users need to view. This is especially so when user experience design creates views of data that cuts across a number of Aggregate types and instances. The more sophisticated your domain, the more this situation tends to occur. we can use multiple repositories to fetch data and assemble them into DTO(Data Transfer Object)
We’d normally see Aggregates with both command and query
methods. We’d also see Repositories that have a number of finder methods that filter on certain properties. With CQRS we are going to disregard these “normalities” and design a different way to query display data.
Now think of segregating all of the pure query responsibilities traditionally
found in a model from all responsibilities that execute pure commands on the
same model. Aggregates would have no query methods (getters), only com-
Separating the write model from the read models helps us separating complex aspects of our domain (who needs what, who is responsible for what) and increase the flexibility of our solution. We can adapt more simply to changing business requirements.
Commands and Queries definitions:
Commands: it is a method that modifies the state of the object, and its method must not return a value.
Queries: it is a method that returns some value, and it must not directly or indirectly cause the modification of the state of the object.
The query model is a denormalized data model. It is not meant to deliver
domain behavior, only data for display (and possibly reporting). If this data
model is a SQL database, each table would hold the data for a single kind
of client view (display). The table can have many columns, even a superset of
those needed by any given user interface display view. Table views can be created from tables, each of which is used as a logical subset of the whole.
we can use SQL views, serialize the data into JSON and save it to Table or using NoSQL DB
A Command is behavior-centric and not data-centric. It is about the intent to change something, it does not map to the format of a resource (like DTOs in an API). It can contain data that will help to process the intent but that’s it.
1- We can use a categorized style with several Command Handlers in one
Application Service. This style creates an Application Service interface and
implementation for a category of commands. Each Application Service could
have multiple methods, one method declared for each type of command with
parameters that fits the category. The primary advantage here is simplicity.
This kind of handler is well understood, easy to create, and easy to maintain.
2- We can create a single class with one method. The method contract facilitates a specific command with parameters. This has clear advantages:
a- There is a single responsibility per handler/processor
b- Each handler may be redeployed independently of others
c- Handler types can be scaled out to manage high volumes of certain kinds of commands.
3- Messaging style of Command Handler. Each command is sent as an asynchronous message and delivered to a handler designed with a dedicated style. This not only enables each command processor component to receive specifically typed messages, but processors of a given type can be added to deal with command processing load. as shown in the previous figure.
Note That Messeging style approach should not be used by default, as it has a more complex design. Instead, start off with either of the other two styles as synchronous command processors. Switch to asynchronous only if scalability demands require it. However asynchronous approach providing temporal decoupling leads to more resilient systems
Updating Query Models:
A special subscriber registers to receive all Domain Events published by the
command model. The subscriber uses each Domain Event to update the query
model to reflect the most recent changes to the command model. This implies
that each Event must be rich enough to supply all the data necessary to produce the correct state in the query model.
Should the updates be performed synchronously or asynchronously?
It depends on the normal load on the system, and possibly also on where the
query model database is stored. Data consistency constraints and performance requirements will influence the decision.
Synchronously: Command and query model normally will use the same database and would update the two models in the same transaction. and the system is under low load.
Asynchronously: If the system is normally under heavy load and the query model update process is lengthy, This may lead to challenges of eventual consistency, where the user interface will not immediately reflect the most recent changes in the command model. The lag time is unpredictable, but it is a trade-off that may be necessary to meet high performance.
Like any pattern, CQRS is useful in some places, but not in others. should only be used on specific portions of a system and not the system as a whole. In this way of thinking, each Bounded Context needs its own decisions on how it should be modeled. So avoid adding unnecessary complexity if not needed.
- Domain-Driven Design by Eric J. Evans
- Implementing Domain-Driven Design by Vaughn Vernon
- Domain-Driven Design Fundamentals course By Julie Lerman and SteveSmith
- Martin Fowler CQRS