CQRS¶
CQRS (Command & Query Responsibility Segregation) is a principle of separating data read requests and data change command requests. According to the CQRS principle, there should be no requests that simultaneously modify and read data. In other words, data should not be modified while preparing a response to a request; forming a response should not have side effects.
Applying the CQRS principle simplifies the program, as the code becomes more readable, debugging becomes more transparent, predictable, and repeatable.
CQRS itself is not a profound architectural paradigm but rather a simple pattern that can be effectively combined with various architectures. The specific implementation of the principle depends on the architecture of the components used in the system, and it can result, for example, in separating queues for data query requests and data modification commands.
Although CQRS is not a self-contained architectural entity but rather a concept or a "tip" regarding common design principles, thoughtful application of CQRS can lead to significant changes in the architectural principles of building an application.
For example, consider the following scenario. Suppose we have a website that displays currency exchange rates, and the backend business logic has the following methods:
# CurrencyInterface
def GetCurrency(CurrencyID) -> Currency: # Working with a single value
def SetCurrency(CurrencyID, Currency) -> None:
def GetCurrencyList(CurrencyID, datetime start, datetime end) -> List[Currency]: # Working with a data sequence
def SetCurrencyList(List[Currency]) -> None:
It seems "normal" - we can read and write both single values and data sequences. At first glance, data for writing and reading are essentially the same, and working with them appears consistent.
However, imagine the website becomes popular, and we need to scale the backend architecture. The site is very popular, and we have deployed the backend on ten servers, each supporting both data writing and reading.
What did we do wrong? To understand, let's conduct another thought experiment. Let's create two interfaces:
# ReadCurrencyInterface
def GetCurrency(CurrencyID) -> Currency:
def GetCurrencyList(CurrencyID, datetime start, datetime end) -> List[Currency]:
# WriteCurrencyInterface
def SetCurrency(CurrencyID, Currency) -> None:
def SetCurrencyList(List[Currency]) -> None:
As the site gains popularity, we think about scaling again, deploying ten servers once more, but... Now the view of our interfaces clearly suggests that we should only scale the reading interface, while the writing interface, available exclusively to administrators and service software updating site information, does not need scalability and can still run on a single server. This was not a secret before while working with the initial combined interface, but the concept of "common interface" and "shared data" clouded this idea, leading to unnecessary costs in scaling a functionality that did not require it.
The CQRS principle brings awareness that processing commands and queries can be fundamentally asymmetric, and symmetric scaling of services often makes no sense.
In summary, it can be said that the CQRS principle itself is quite banal, simply providing a mental push to think about the application architecture; the challenges (and advantages) will lie more in the area of architectural decisions you make for integrating the two separate data streams using CQRS.
Go to RU version of this page.
codifycamera.com © CC BY-NC-SA 4.0 2024 — ∞ Mikhail Emelyanov, war4one@gmail.com