We continue our series in the chapter 4 about Architecture π
A. What's the problem ?
How can we retrieve all the data users need to view and do it correctly and especially when the domain is complex ? we can do it with repositories in combination with DTOs but it will not be optimal
B. The Solution
The answer lies in the oddly named architecture pattern CQRS ( Command Query Responsibility Segregation ). It is the result of pushing a stringent object (or component) design principle, command-query separation (CQS), up to an architecture pattern.
C. The Definition
C.1. Basic
Bertrand Meyer, asserts the following:
Every method should be either a command that performs an action, or a query that returns data to the caller, but not both. In other words, asking a question should not change the answer. More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.
C.2. Technical
1. If a method modifies the state of the object, it is a command, and its method must not return a value. In Java and C# the method must be declared void.
2. If a method returns some value, it is a query, and it must not directly or indirectly cause the modification of the state of the object. In Java and C# the method must be declared with the type of the value it returns.
D. how is it applied ?
There is two steps, designing the Command Model and after tuning the Query Model
D.1. Command Model
Now think of segregating all of the pure query responsibilities traditionally found in a model from all responsibilities that execute pure commands on the same model. Aggregates would have no query methods (getters), only command methods. Repositories would be stripped down to an add() or save() method (supporting both creation and updating saves) and only a single query method, such as fromId(). The single query method takes the unique identity of an Aggregate and returns it. A Repository could not be used to find an Aggregate by any other means, such as by filtering on some additional properties. With all of that removed from the traditional model, we designate it a command model.
D.2. Query Model
To fully understand the design of the Query Model we have to do a deep dive in the details of the CQRS Pattern for each of the components of the figure below :
D.2.A Client and Query Processor
The client (at the far left in the diagram) may be a Web browser or a custom desktop user interface. It uses a set of query processors running on a server. The diagram doesnβt show architecturally significant divisions between tiers on the server(s). Whatever tiers exist, the query processor represents a simple component that only knows how to execute basic queries on a database, such as a SQL store.
D.2.B Query Model (or Read Model)
The query model is a denormalized data model. It is not meant to deliver domain behavior, only data for display (and possibly reporting). If this data model is a SQL database, each table would hold the data for a single kind of client view (display). The table can have many columns, even a superset of those needed by any given user interface display view. Table views can be created from tables, each of which is used as a logical subset of the whole
D.2.C Command Processors
A command submission is received by a Command Handler/processor, which can have a few different styles. We consider those styles here, along with some advantages and disadvantages.
We can use a categorized style with several Command Handlers in one Application Service. This style creates an Application Service interface and implementation for a category of commands. Each Application Service could have multiple methods, one method declared for each type of command with parameters that fits the category. The primary advantage here is simplicity. This kind of handler is well understood, easy to create, and easy to maintain.
We can create a dedicated style handler. Each one would be a single class with one method. The method contract facilitates a specific command with parameters. This has clear advantages: There is a single responsibility per handler/processor; each handler may be redeployed independently of others; handler types can be scaled out to manage high volumes of certain kinds of commands.
D.2.D Command Model (or Write Model)
Executes Behavior As each command method on the command model is executed, it completes by publishing an Event. This is the linchpin for updating the query model with the most recent changes to the command model. If using Event Sourcing, the Events are also necessary for persisting the state of the Aggregate that has just been modified. However, it is not a necessity to use Event Sourcing with CQRS. Unless Event logging is a requirement specified by the business, the command model can be persisted using an object- relational mapper (ORM) to a relational database or some other approach. Either way, a Domain Event must still be published to ensure that the query model is updated.
D.2.E Event Subscriber Updates the Query Model
A special subscriber registers to receive all Domain Events published by the command model. The subscriber uses each Domain Event to update the query model to reflect the most recent changes to the command model. This implies that each Event must be rich enough to supply all the data necessary to produce the correct state in the query model.
N.B : Should the updates be performed synchronously or asynchronously? It depends on the normal load on the system, and possibly also on where the query model database is stored. Data consistency constraints and performance requirements will influence the decision.
E. Eventual Consistency
E.1. Trade-off
To update synchronously, the query model and command model would normally share the same database (or schema), and we would update the two models in the same transaction. That keeps both models completely consistent. Yet, this will require more processing time for the multiple table updates, which may not meet the service-level agreement (SLA). If the system is normally under heavy load and the query model update process is lengthy, use asynchronous updates instead. This may lead to challenges of eventual consistency, where the user interface will not immediately reflect the most recent changes in the command model. The lag time is unpredictable, but it is a trade-off that may be necessary to meet other SLAs.
E.2. A way to give a solution
One technique suggested by [Dahan, CQRS] always explicitly displays on the user interface the date and time of the data from the query model that a user is currently viewing. To do so, each record in the query model needs to maintain the date and time of the latest update. This is a trivial step, generally supported by a database trigger. With the date and time of the latest update, the user interface can now inform the user how old the data is. If the user determines that the data is too stale to use, he or she can at that time request fresher data. Admittedly this approach is lauded by some as an effective pattern and heavily criticized by others as a hack or artifice. Certainly these opposing viewpoints indicate the need to perform user acceptance tests before this approach is employed in our own systems.
Conclusion : As with every pattern, CQRS introduces a number of competing forces. We must exercise a great deal of care and choose wisely.
See you next time π by the grace of JESUS, This year i will try to be more consistent !
Top comments (1)