Are you dealing with an application that involves complex business logic?
If that’s the case, then Command and Query Responsibility Segregation (CQRS) architectural pattern might be the solution you were looking for.
In recent years the focus on improving user experience has been notable -
The way users perceive applications has changed enormously.
Nowadays, users expect applications to be lighting fast, especially when performing queries.
The problem is that as applications become more complex, the handling of detailed queries and validation rules also grows in complexity. In this type of scenario, the conventional Create, Read, Update, and Delete (CRUD) data model could become cumbersome to implement and maintain.
In this article, Introduction to CQRS, we will explore the principles behind the CQRS pattern, as well as its pros and cons, to assist you in deciding whether or not the CQRS pattern is right for your project.
How CQRS works
The best way to illustrate how CQRS works is with an example. Let’s say you are designing a simple todo application — Following the traditional CRUD data model, you may implement a service in charge of creating, listing, updating, and deleting tasks. A simple solution that reads/updates the data store.
The Command and Query Responsibility Segregation (CQRS) pattern propose separating the write data model from the read data model. This separation of responsibilities would provide the flexibility to decide whether the read and write services should coexist in the same data store or be managed in completely different databases.
In the CQRS context, commands are methods whose sole purpose is performing an action. Simply put, commands in our example would be responsible for creating, updating, and deleting tasks (write operations). In the CQRS architecture, commands cannot return data, since that functionality is unique to queries. In that regard, queries are methods that are only able to read and return data without modifying it. Therefore, in our example queries would perform read-only operations such as listing tasks or returning the status of a particular task.
The implications of CQRS architecture to microservices is huge. In essence, CQRS takes microservices to the next level by splitting CRUD operations into two independent models that can be managed separately.
— How is exactly the CQRS pattern related to microservices
Without a doubt, microservices has changed the way applications are conceived. The monolithic model has transitioned to a decentralized architecture where each service can be scaled and optimized independently. CQRS adds another layer of scalability, performance, and flexibility to microservices by allowing granular read-write optimizations at the database level.
● Different users normally require unique data representations associated with their roles that may include complex queries. Thanks to CQRS, the read side can be optimized to address such requirements.
● Business logic usually involves complex validations to ensure that each user is granted with proper permissions. Since CQRS separates read and write sides, the complexity of domain logic can be isolated and optimized more efficiently.
● Separation of the CRUD data model into specialized and independent services is ideal since it allows taking full advantage of microservices architecture benefits.
Combining microservices architecture with the CQRS pattern brings many advantages but also has some shortcomings that are discussed next.
CQRS benefits in a nutshell
Facilitates distributing dev workload. By separating read and write concerns large teams can manage application development more efficiently. Depending on business logic complexity, developer managers can assign more resources where they are needed the most.
Easier maintenance. The flexibility of redistributing the team at will makes maintenance and bug fixing more efficient since resources can be allocated on demand.
Optimized database design. Arguably one of the most appealing advantages of the CQRS pattern is the ability to optimize the read side and therefore enhance query performance significantly. Thanks to CQRS, the read model can be optimized for queries, giving the users the freedom to manipulate data views, create detailed filters, custom projections, and more without hindering overall application performance. Similarly, the write side can be designed without the limitations associated with read-intensive applications, meaning better use of database resources.
Easier scalability. Another key benefit of using CQRS in microservices has to do with the ability to scale read and write services independently. Since each application is unique, it’s difficult to predict how it could evolve over time. CQRS read/write separation has the advantage of splitting the data management which allows different scalation strategies. In most cases that means the read side can be scaled faster and more efficiently, preserving application performance and user experience.
Enhanced security. Nowadays, any technology that helps to harden overall security is welcome. That’s the case of CQRS. By separating write and read operations, it is easier to isolate and decide which domains entities have access to a particular dataset and which ones will have the permissions to write or modify data.
CQRS key disadvantages
Added complexity. Although splitting read and write into separate models seems logical, truth is it adds complexity to application design. The level of complexity will depend on the application itself. However, similar to a microservices architecture, this complexity is compensated by the flexibility, performance, and scalability that CQRS offers.
Eventual consistency. Separating read and write databases does not come without challenges. Keeping the read model updated is one of them. That explains why using Event Sourcing and CQRS together is so common. Event Sourcing solves the consistency issue but also adds another layer of complexity to the design.
CQRS vs CRUD, which is better?
The short answer is, it depends. The CQRS pattern is not recommended when the application business logic is straightforward. Since CQRS adds complexity to the application design, it has no sense “forcing” the CQRS pattern when the application can work using CRUD-style operations without any scalability issue.
On the other hand, CQRS could bring substantial benefits in the following scenarios:
Overly difficult business logic. The CQRS pattern can help to reduce business logic complexity by isolating read and write concerns.
Large collaborative applications. In scenarios where a massive amount of users access data in parallel, the ability to isolate, scale, and fine-tune query operations make CQRS a viable solution.
Scenarios needing high read and write performance. Applications requiring fine-tuning both, the reading performance and the writing performance, can take advantage of the CQRS pattern since it allows independent scalation of each model.
Applications that integrate with Event-driven architecture. As said earlier, CQRS and Event Sourcing are often used together since they complement each other perfectly.
Keep in mind that, in complex applications with multiple bounded contexts, nothing prevents you from implementing the CRUD data model in some contexts, and the CQRS pattern in others, leveraging the strengths of each architecture.
Implementing CQRS in your application can maximize its performance, scalability, and security. However, you should carefully consider whether your application is really worth the additional effort involved in such an implementation since, in many cases, the solution offered by the CRUD model is more than enough.
Does this dilemma sound familiar?
Deciding whether or not implementing the CQRS pattern leads to challenges similar to those you face when deciding between monolithic applications vs. microservices-based applications. In the end, you will have to ponder if the benefits justify the time and energy required.
Daniel Chernenkov, a software engineering manager @ Bigabid and technical advisor for startups.