Blog

Scaling Microservices with CQRS Pattern

1 SgZahjPAoEg8LRiBP3z1jg

CQRS stands for Command and Query Responsibility Segregation, a pattern that separates read and update operations for a data store. CQRS is an architecture pattern often mentioned in connection with domain-driven design (DDD) and Event-Sourcing. Greg Young coined the term in 2010, and the content of CQRS is based on Bertrand Meyer’s CQS design pattern.

Problem

In traditional architectures, the same data model is used to query and update a database. That’s simple and works well for basic CRUD operations. In more complex applications, however, this approach can become unwieldy. For example, on the read side, the application may perform many different queries, returning data transfer objects (DTOs) with different shapes. Object mapping can become complicated. On the write side, the model may implement complex validation and business logic. As a result, you can end up with an overly complex model that does too much.

Read and write workloads are often asymmetrical, with very different performance and scale requirements.

CQRS Design Pattern

CQRS is one of the important pattern when querying between microservices. We can use CQRS design pattern in order to avoid complex queries to get rid of inefficient joins. CQRS stands for Command and Query Responsibility Segregation. Basically this pattern separates read and update operations for a database.

Normally, in monolithic applications, most of time we have 1 database and this database should respond both query and update operations. That means a database is both working for complex join queries, and also perform CRUD operations. But if the application goes more complex this query and crud operations will be also is going to be un-manageable situation.

1 1BEvfEBNBseNjrVrPeE9PA

CQRS In Microservices World

Microservice architecture, also known as just “microservices,” is an approach to building software in which applications are separated into components called services, which are loosely coupled but function autonomously. Services are usually deployed independently so that a failure or outage of one does not affect the others. They are typically closely aligned with a particular business function or objective.

By using the microservice architecture, a company can create new products and services faster and better orient development teams toward specific business goals. A microservices architecture can be organized around business capabilities, enabling teams to track towards and deliver on specific goals.

Microservices based on goals and capabilities such as these allow for concurrent builds, with many developers working on the same microservices applications at the same time, ultimately shortening the development cycle for internal teams.

Benefits of microservices

  • Faster time to market: The microservice method significantly reduces development time. Changes or updates to an individual service can be immediately applied, tested, and implemented, with no impact on the rest of the system. This is opposed to the older method of building “monolithic” applications, in which even small code changes require redeploying the entire software stack, which introduces risk and complexity.
  • Modularity: Services usually focus on a specific objective and are decoupled along business boundaries. This keeps teams focused on relevant goals and ensures autonomy between services. Each service is housed in its own software container, and multiple instances of the same microservice architecture are typically deployed for redundancy and scalability.
  • Flexibility and scalability: Monolithic architecture for an application requires the entire system (and all of its functions) to scale simultaneously. With a microservice architecture, only the components or features that require extra performance need to be scaled. This is easily achieved by deploying more microservice instances, enabling better capacity planning and keeping a lid on software licensing costs.
  • Resiliency: With a monolithic application, the failure of a single component can compromise the entire system. In microservices, each service is isolated to prevent cascading failures from bringing everything down. In the unlikely event that all instances of a microservice fail, its performance would degrade but other application components would continue to deliver value.
  • Organizational alignment: Microservices ensure that technical groups and other teams are right-sized and well aligned with business needs and required tasks. Teams are typically small, interdisciplinary, and centered around a single component of the overall application. This cross-functional approach breaks down silos, improves collaboration, and is especially effective for distributed and remote teams.
  • Reductions in cost: Microservices lower costs by utilizing infrastructure and development teams more efficiently. Maintaining the large codebase of a monolithic application consumes significant time and resources. In contrast, the services within a microservices-built system can each be managed independently, greatly reducing the time spent on maintaining and updating code.

CQRS thus also offers all the advantages of a microservices architecture, such as the individual scalability, maintainability, and testability of the individual services. The operation of different versions of business logic is also easy with CQRS, and the access rights of the individual services can be specifically restricted, which in turn benefits the security of the overall system.

However, the disadvantage lies in an inherently more complex architecture than a traditional client-server system — but it should be borne in mind that CQRS also offers several advantages, which come at a price.

One of the greatest strengths of CQRS is the possibility, especially in connection with DDD and Event-Sourcing, to separate the technical code from the business code. The business logic can therefore be adapted without changing anything in the technical substructure. The same applies, more importantly, vice versa, which is conducive to long-term stability and absolute confidence in business logic.

Due to the complexity mentioned, it is advisable to consider whether to build your application on a suitable framework that already implements CQRS and Event-Sourcing so that as a developer, you can primarily concentrate on designing and writing the technical code can. Such frameworks are available for numerous technologies, languages, ​​and platforms such as Spring (Java) or NestJS (TypeScript, NodeJS).

When to use CQRS pattern

Consider CQRS for the following scenarios:

Collaborative domains where many users access the same data in parallel. CQRS allows you to define commands with enough granularity to minimize merge conflicts at the domain level, and conflicts that do arise can be merged by the command.

Task-based user interfaces where users are guided through a complex process as a series of steps or with complex domain models. The write model has a full command-processing stack with business logic, input validation, and business validation. The write model may treat a set of associated objects as a single unit for data changes (an aggregate, in DDD terminology) and ensure that these objects are always in a consistent state. The read model has no business logic or validation stack, and just returns a DTO for use in a view model. The read model is eventually consistent with the write model.

Scenarios where performance of data reads must be fine-tuned separately from performance of data writes, especially when the number of reads is much greater than the number of writes. In this scenario, you can scale out the read model, but run the write model on just a few instances. A small number of write model instances also helps to minimize the occurrence of merge conflicts.

Scenarios where one team of developers can focus on the complex domain model that is part of the write model, and another team can focus on the read model and the user interfaces.

Scenarios where the system is expected to evolve over time and might contain multiple versions of the model, or where business rules change regularly.

Integration with other systems, especially in combination with event sourcing, where the temporal failure of one subsystem shouldn’t affect the availability of the others.

This pattern isn’t recommended when:

The domain or the business rules are simple.

A simple CRUD-style user interface and data access operations are sufficient.

Consider applying CQRS to limited sections of your system where it will be most valuable.