In the world of software architecture, there's a common misunderstanding about the Command Query Responsibility Segregation (CQRS) pattern. Many believe it requires using two databases, a messaging queue, that it introduces complexities into the architecture, or needs Eventual Consistency and Event Sourcing.
However, it's crucial to clarify that these elements, though often linked with CQRS, aren't mandatory prerequisites.
In this series of blog posts, we’ll dive into three approaches to CQRS:
- (This article) Simple CQRS: CQRS in its most simple form.
- Typical CQRS: CQRS in its most common approach, typically including event sourcing and separate databases.
- Practical approach to CQRS: My suggested approach to CQRS, including practical examples.
Fundamentally, CQRS is a straightforward pattern, emphasizing a clear separation between commands and queries. So, what is CQRS, stripped of unnecessary complexities and misconceptions?
Glossary
CQRS: The CQRS pattern, which can be in its simple form or the more common typical approach.
Simple CQRS: Basic CQRS implementation, separating commands and queries into distinct classes.
Typical CQRS: The commonly recognized form of CQRS, which includes event sourcing and separate databases.
What is CQRS?
Greg Young mentions in his article named 'CQRS, Task Based UIs, Event Sourcing agh!’
"CQRS is simply the creation of two objects where there was previously only one. The separation occurs based upon whether the methods are a command or a query."
And continues to provide the following definition:
CODE: https://gist.github.com/flomosq/0ddae20d63d067b729ed94bf0a447d9e.js?file=CustomerService.rb
After applying CQRS to that code, he presents the following result:
CODE: https://gist.github.com/flomosq/1db6510c31d9f4ac30fae8502c6a2cba.js?file=CustomerService.rb
Therefore, contrary to common associations, CQRS is a straightforward pattern that involves creating a command object and a query object, where there previously was one object doing reads and writes.
The illustration below shows the essence of CQRS:
Source: https://medium.com/docplanner-tech/the-essence-of-cqrs-90bdc7ee0980
What CQRS is not
Despite its benefits, there are common misconceptions surrounding CQRS. This section aims to debunk these myths and highlight the inherent simplicity of the pattern. By clarifying these misconceptions, developers can approach CQRS with a clearer understanding.
In his post from 2012, titled “CQRS”, Greg Young says:
- CQRS is not a silver bullet
- CQRS is not a top level architecture
- CQRS is not new CQRS is not shiny
- CQRS will not make your jump shot any better
- CQRS is not intrinsically linked to DDD
- CQRS is not Event Sourcing
- CQRS does not require a message bus
- CQRS is not a guiding principle / CQS is
- CQRS is not a good wife
- CQRS is learnable in 5 minutes
- CQRS is a small tactical pattern
- CQRS can open many doors
Let's delve into each of his statements to gain a deeper understanding:
- CQRS is not a silver bullet: CQRS is not a universal solution that solves all problems. It's a pattern with specific use cases and benefits, but it might not be suitable for every scenario. Understanding the context and requirements of a system is crucial before deciding to adopt CQRS.
- CQRS is not a top-level architecture: CQRS is not meant to be the overarching architectural style of an entire system. It's a tactical pattern that can be applied selectively to specific components or modules within a larger architecture.
- CQRS is not new: CQRS principles have been around for a while, and they are not a recent invention. The idea of separating commands and queries has historical roots in software design.
- CQRS is not shiny: CQRS might not have the allure of being a trendy technology. It's a practical pattern designed to address specific challenges.
- CQRS will not make your jump shot any better: This statement humorously underscores that CQRS is a software design pattern and not a magic solution.
- CQRS is not intrinsically linked to DDD (Domain-Driven Design): While CQRS is often used in conjunction with DDD, they are not inseparable. You can apply CQRS without strictly adhering to DDD principles, and vice versa.
- CQRS is not Event Sourcing: Event Sourcing is a related but distinct pattern. While CQRS and Event Sourcing are often used together, they can also be applied independently. CQRS focuses on separating read and write concerns, while Event Sourcing involves capturing and storing the history of state-changing events.
- CQRS does not require a message bus: CQRS doesn't mandate the use of a message bus. While a message bus can be beneficial for communication between components, it's not a strict requirement for implementing CQRS.
- CQRS is not a guiding principle / CQS is: Command Query Separation (CQS) is a guiding principle that emphasizes the separation of commands and queries. CQRS is an application of CQS in a more specialized context.
- CQRS is not a good wife: This metaphorical statement suggests that CQRS is not a comprehensive or perfect solution for every situation. It's a tool with strengths and weaknesses, and its applicability depends on the specific needs of the system.
- CQRS is learnable in 5 minutes: Greg Young implies that the basic concept of CQRS can be understood relatively quickly. And the truth is, in its basic form, it is easily learnable in 5 minutes.
- CQRS is a small tactical pattern: CQRS is a specific, tactical pattern rather than a broad architectural philosophy. It can be applied selectively to solve particular challenges within a system
- CQRS can open many doors: Despite its specific nature, CQRS can bring valuable benefits when applied judiciously. It can open doors to improved scalability, flexibility, and maintainability in systems with complex business requirements.
Benefits of Simple CQRS
CQRS on its own is a simple pattern, but it offers several advantages worth considering, particularly when separating command and query responsibilities. These benefits are outlined below.
- Improved clarity and readability: The code becomes more focused and easier to understand. The classes responsible for modifying data (commands) and the ones responsible for retrieving data (queries) are easily identifiable.
- Support for complex domains: It simplifies complex domains, allowing developers to work on each side separately.
- Simplified testing: It becomes easier to write unit tests that specifically target either the modification or retrieval of data.
- Easier integration with third-party systems: It can make it simpler to integrate with external services or APIs.
- Encourages Single Responsibility Principle (SRP): CQRS promotes the Single Responsibility Principle since each class has a clear and singular purpose.
Difficulties with Simple CQRS
- Increased complexity: Introducing CQRS often involves restructuring the application architecture to separate command and query responsibilities. This restructuring can add complexity to the codebase, especially if the application was not initially designed with CQRS in mind.
- Learning curve: Adopting CQRS requires developers to understand and embrace the principles of command-query separation. This shift in mindset may require time and effort for developers who are used to traditional applications that do not explicitly encourage CQRS.
- Overhead and maintenance: Maintaining separate code paths for commands and queries can introduce additional overhead, such as managing separate models, validators, and database interactions. This increased complexity can make the codebase more challenging to maintain.
- Potential for over-engineering: Without careful consideration, developers may be tempted to apply CQRS to every aspect of the system. It's important to apply CQRS thoughtfully, and only where it provides clear benefits based on the specific requirements of the application.
- Consistency between command and query models: Maintaining consistency between the models used for commands and queries can be challenging, especially as the application evolves over time. Changes to one model may require corresponding updates to the other, which could introduce errors and inconsistencies.
As beneficial as CQRS might be, it's important to ensure that the drawbacks do not overshadow its advantages. It's crucial to keep a watchful eye, especially on existing applications that were not originally designed with CQRS in mind, as the introduction of this pattern may introduce complexities that outweigh its benefits.
When to apply Simple CQRS
CQRS is a pattern that should be considered when there is a requirement to segregate the responsibilities of modifying and reading the application state. It offers significant benefits in complex domains.
The CQRS pattern is commonly applied in various use cases, including:
- Complex domain logic: For applications that involve complex domain logic that can benefit from separating commands (write operations) and queries (read operations), CQRS is recommended. This separation can enhance clarity and maintainability.
- Scalability requirements: Consider applying CQRS when your application requires scalable write and read operations. By separating commands and queries, you can optimize each path independently, allowing for better scalability.
- Simplified testing: CQRS can be very helpful for applications that require simplified testing. By having distinct command and query paths, it becomes easier to write targeted tests for each operation.
CQRS can be used on specific portions of a system (e.g. bounded contexts in DDD) and not the system as a whole. It requires a significant mental shift and should only be adopted if the benefits outweigh the added complexity and risks.
When not to apply Simple CQRS
While CQRS offers notable advantages in certain scenarios, there are situations where its adoption may not be appropriate. Here are some instances when CQRS might not be the best fit:
- Simple CRUD: If the application is a simple CRUD without much domain complexity, implementing CQRS might introduce unnecessary complexity.
- Tight budget or time constraints: If the team is new to CQRS, it can add overhead in terms of design and development. This might not be justifiable in such cases, specially in already existing applications where a refactor might be needed.
- Team experience and expertise: While CQRS is easy to understand, it requires a shift in mindset and that could have an impact in development time.
How to apply Simple CQRS?
Implementing CQRS in your application involves segregating the layer responsible for handling data operations into command and query responsibilities. This segregation can occur at various levels such as the database, model, service, or any other relevant layer. Let's explore a simplified approach to applying CQRS in Ruby.
Step 1: Identify the Segregation Point
Determine where in your application architecture you want to introduce the segregation of command and query responsibilities. This could be at the level of database interactions, within domain models, or in service layers.
In our example, we'll choose the service layer.
Step 2: Separate into Command and Query Responsibilities
Once you've identified the segregation point, split the responsibilities into commands for actions that modify data and queries for actions that retrieve data.
Say this was our previous code:
CODE: https://gist.github.com/flomosq/088683d366b393e9f344d696b32efa45.js?file=customer_service.rb
Then applying CQRS in this layer would result in:
CODE: https://gist.github.com/flomosq/141e5b417cd9539f20ae785ae2e80ad6.js?file=customer.rb
Step 3: Usage
CODE: https://gist.github.com/flomosq/e93a4a21898367dfd97862187f809284.js?file=customers_controller.rb
TL;DR
CQRS is often misunderstood as a complex solution requiring significant mindset shifts. It's mistakenly associated with event sourcing and separate databases as a one-size-fits-all solution.
However, Simple CQRS, while beneficial (mostly for maintainability), requires careful consideration before implementation due to its mindset shift. It's crucial to assess when and how to apply it effectively.
Key Takeaways
- CQRS, often misunderstood, is a straightforward pattern emphasizing separation of command and query responsibilities.
- Contrary to misconceptions, CQRS doesn't require complex elements like separate databases or messaging queues.
- CQRS is not a universal solution and isn't suitable for every scenario.
- It can bring significant benefits when applied carefully, including improved clarity, support for complex domains, simplified testing, easier integration with third-party systems, and single responsibility principle.
- While beneficial, implementing CQRS introduces challenges like increased complexity, a learning curve, overhead and maintenance, potential over-engineering, and a challenge for consistency between command and query models.
References
CQRS facts and myths explained - Event-Driven.io
CQRS, Task Based UIs, Event Sourcing agh! | Greg Young