Before we dive in, let’s investigate classic architecture. It has four layers. Each layer has its own responsibility, such as a domain always handles invariants and business rules of the project, and the service layer is responsible for basic validation and a communication point to the domain layer. When your system grows, you need to make some changes to adjust. Often, multiple users want to browse or modify the same set of data. In such an environment, the returned data to the user may not be same as the data in the database. It may be modified and the user performs some action on old data.
The reasons for this kind of inconsistency is having the one database and caching on that database with consideration to improve performance. I mean Stale data. But, this kind of changes is not effective enough. This is because the core is still the same and not suitable for your newly grown application and it is not scalable enough.
I prefer the Domain-Driven Design approach for the projects that are grown and there is no place for them in classical architecture. But, in Domain-Driven design approach, the architecture of some bounded contexts, there are still bottlenecks that will reduce your read/write operations. For example, the model is located at heart of the application and it’s not unexpected to have complicated aggregates and sophisticated business rules.
If you try to query this kind of model, you may add some code for querying purposes, index searching, and other actions related to reporting issues, because reporting typically is used more. It may compromise your domain model because you should have some strategy to facilitate a reading operation. Such merging aggregates and writes reading code in the repository that is not suitable for reading and brings too much overhead. You can avoid these issues by applying CQRS architecture and freeing the model from any presentational requirements.
What Is CQRS?
Asking a question should not change the answer.
CQRS originated with Bertrand Meyer’s CQS. The difference is that, in CQRS, you have a sperate object for both command and query. CQRS is fundamentally about denormalization.
CQRS stands for Command Query Responsibility Segregation. It was introduced by Greg Young. It postulates that every method should either be a Command that performs an action or a Query that returns data. A command cannot return data and a query cannot change the data. Each model can be optimized for its specific context, and it also retains its conceptual integrity.
In CQRS, we have a command/handler pattern for commands (writing purpose). The user sends a command and the desired handler is responsible for coordinating state changes. What if you ask about the necessity of the command/handler pattern? The command pattern will let your project to be more scalable (I will describe further) and prevent anemia. Also, the most important thing is making sure the invariants of our business rules are always consistent. For example, if we define invariants of our domain model out of it, we have to define them separately each time and there is no guaranteed consistency.
There are three kinds of CQRS: Standard, Event-sourcing, and Eventual consistency. I will describe each of them further. You can have one database and two sperate models. It’s a good idea; the result is immediate and synchronize. However, for other types of applications, it might be desirable to use two different data stores.
You may ask why we shouldn’t use the same pattern for reading and writing to deal with code reuse purposes. Udi Dahan wrote an article about the fallacy of reuse. He believes:
|Reuse may make sense in the most tightly coupled pieces of code you have, but not very much anywhere else.|
On the query side, there are just DTO objects and no Domain Model, repository, or other common Domain-Driven Design implementations. Feel free to generate views from your model directly. The query side is responsible for retrieving data, data which is purified and suitable for reporting purposes. The query side has no need to pave a domain model and bottlenecks; we can de-normalize data and optimise it in a shape that the consumer wants. We can have a separate database for each action and synchronize them.
For implementing on the Query side, you may go directly to the database and do some querying over there. That’s works, especially when performance and efficiency are a big deal. In this case, I do recommend a light SQL library instead of ORMs. ORMs are anti-patterns and are not good for reading purposes. Someone may ask, without ORM there would be a query duplication challenge? Yes, one of the compliments about the direct query is DRY; you may have to do some domain model actions (calculations and …) again. Duplication of domain logic is not good and it may cause failure of the system.
The other option is using current domain objects. This option is good for quick reporting. You extend reporting objects from existing domain objects. The performance will decrease significantly, but the speed of development increases. At first sight, a simple mapping between domain object and reporting is desired. Unfortunately, the benefits do come with a cost. This will lead you to big performance trouble, and the developer is dealing with a problem of Impedance Mismatch.
Database tables and class objects are orthogonal. In the database, there is no way to define invariants and business rules. You may ask about using if statements in queries or other conditional statements, but they are far their object equivalence, and make your object anemic. Also, the relational model does not support any sort of polymorphism or IS-A kind of relation, so developers eventually find themselves adopting one of three possible options to map inheritance into the relational world.
A command is a business use case. You should write the command in the language of the business use case of the system, not in ubiquitous language. The command is not same as the concept of the domain model. Exactly, in fact, the command is business use case driven. In general, the command is a DTO with a simple validation. Now, you may ask about how we can differ business use case language from the ubiquitous language. This issue has been already solved by the application service layer.
Let’s deep dive in and investigate. The application service layer (as you might already read my previous articles about Domain-driven design) sits above the domain layer and is responsible for interpreting business use case language to domain language and coordinate all actions. In my experiences in enterprise application development, almost all junior developers confuse domain logic (aka business logic, business rules, and domain knowledge) and application logic. This confusion will compromise your system. Let’s find out a way to make sure your choice is correct.
One of the big signs of application service layer logic is its responsibility to handle infrastructural concerns. Also, as I mentioned earlier, it’s responsible to coordinate business use cases with the domain logic of the application. Distinguishing infrastructural concerns is not a big deal and you may find it easy.
What about the second duty of application service. It’s harder, isn’t it? Application service logic always delegates to the domain logic and it should not make business-critical decisions. One of the techniques in finding domain model business rules is asking a question about the use case. For example, Is this mandatory? or Are these steps inextricable? These steps must come with each other always, and if you found a way to recombine, potentially it’s not a domain concept.
After making sure our domain model is completely isolated, it’s time to know the patterns which help ensure our application service has no undesirable coupling. I list the patterns next:
- Command Processor pattern: You have a command and a processor for each use case.
- Publish/Subscribe pattern: A pattern for looser coupling.
- Request/Reply pattern: Follows the One Model In-One Model Out approach.
- Async/Await pattern: A pattern for building asynchronous, non-blocking applications.
The commands are an intent and can be rejected. They always execute against the aggregate root; with aggregate root, you can force your invariants and make sure everything is right. In fact, aggregates make the decision to accept or reject commands. The system might have lots of commands and the aggregate only stores (writes) the commands which are accepted and switch to a new state.
CQRS and Event Sourcing
Event Sourcing is not necessary for CQRS. You can combine Event Sourcing and CQRS. This kind of combination can lead us to a new type of CQRS. It involves modeling the state changes made by applications as an immutable sequence or log of events. You may think about logging in your system and event logging, but, to be honest, event logging is not event sourcing. Event sourcing forces the current state to be derived from history. If you cannot reason about your current system from history, you are not doing event sourcing. Indeed, events are business facts.
In Domain-Driven design, events must follow Ubiquitous language and all the events in your system must be in the past and named in past tense. Events are independent—events should have enough data to describe themselves.
Event sourcing is rising in popularity because it makes troubleshooting easier and has better performance characteristics; writes and reads can be scaled independently. References to GRASP event souring enable loosely coupled application architecture. Also, it enables you to add more applications in the future that need to process the same event but create a different materialized view.
Events sourcing acts as a time machine. This is known as a Temporal Query. Events will keep data in your system and let you describe and interpret it on your own. You may have multiple ways to do a task. You cannot delete your events because they are immutable, even when you have incorrect data in your event store. Exactly, in fact, events are facts and cannot be deleted. There are some downsides, such as a higher learning curve and unfamiliar programming model.
An aggregate that accumulates an unbounded number of events over time is a smell. For example, say you have hundreds of events in your event store and you need to reply to events to get the desired result. If you reply to all events, you are faced with performance problems and you may not need events to be replied. A snapshot is a bookmark in a series of your events; it acts as the starting point in event replies. Exactly, in fact, the snapshot is same as the Memento pattern. It is only a technical definition for optimization and in the conceptual model of event-souring has no meaning or full definition.
It’s explicit that every client needs to read and display information and usually is interested in the current state. Event store alone is not able to represent current state. It needs some help to query; let’s call it a query handler. A query handler builds up a projection. A projection effectively is a current state representation that can be sourced from the events.
A query handler uses projection instead of the event source. You can have many projections that are created and optimised for a specific query. Projections are built by projectors, which are processes of event streams. Of course, they are not using the observer pattern. Projectors know where the event streams are; this will let them find the pointer.
You can rebuild a projection by resetting the pointer to zero. Projections and projectors are agnostic. Projection can use denormalized data and can be defined by each new interpretation. They can switch between projectors easily; this means there is no need for database migration anymore.
If a command is responsible for writing and a query is responsible for reading, Reactors (reactions) are for all of the business logic between them. Reactors process an event stream as projections do, but they do not maintain as projections for querying purposes. They also have a pointer to show where there are. We can say projectors are kind of reactors, but instead of creating a projection, they react to events, either through trigger external behavior or by omitting new events back to event store, or both.
Reactors read event streams and subscribe to them. They are responsible for triggering business rules of events. Reactors keep referenced data, which sometimes call internal projections and watch the stream of events and react when the condition is met. They execute a side effect from something that happened and then emit an event to that event.
Reactors are orthogonal. They are asynchronous and are subjects for eventual consistency. Reactors are encapsulated—they are completely isolated and self-sufficient. Sagas are built by a series of reactors.
Let’s talk about the other type of CQRS, the Eventual Consistency. Some systems are tightly coupled, as in a consistent Web application that is is talking to a database directly. This kind of system id not scalable enough. Indeed, the faster you go, the less scalable you will be. To have scalable systems, we need to have a loosely coupled system that, instead of talking directly to the database, will put data in the queue and later will stick it on the database. Then, you should guarantee the user request is registered in the queue and that the queued data will be stored in the database. It’s actually eventual because everything that happened will store with a latency, even with a millisecond.
Let’s see another example. Imagine in Instagram you have a picture; when everyone likes the picture, you send a post request to the server. The server store likes in a NoSQL database and, in a certain period, such every 30 seconds, will process data and store it in a relational database. It’s a clear example of Eventual Consistency. The client will not impact on the database directly and the system is allowed to make a decision about how and when to process the requests.
One of the important aspects of Eventual Consistency is about user experience. As a user, when you book a hotel room, some systems will not approve the booking immediately; it just gets your request and then notifies you via e-mail (ask the hotel if they have enough rooms). In this situation, the user can cancel request without loss of money (cancellation policy of hotel). Because of this, the request is eventual and the user may have a chance to cancel before the request is sent to the hotel. I do recommend Udi Dahan’s Race Conditions Don’t Exist article.
CQRS has many advantages to some of the most difficult problems when building complex and large-scale applications. However, this type of architecture is specially tuned to solve a very specific subset of problems.