With the data in our applications it is like with source code: It's more often read than written. While classic enterprise applications often have thousands of read accesses to a write access (think of the canteen plan, the heart and soul of many corporate intranets), the ratio of read to write accesses in current web applications is typically between 7 and 10 to 1.
The term Separation of Concerns , in other words the separation of different concerns, goes back to the computer science pioneer Edsger Dijkstra . He wrote in a paper in 1974:
But nothing is gained – on the contrary! – by tackling these various aspects simultaneously. It is what I sometimes have called the separation of concerns , which, even if not perfectly possible, is yet the only available technique for effective ordering of one’s thoughts, that I know of.
Over the years Separation of Concerns has become a central golden rule in programming. Many errors in design or programming can be explained by the fact that different concerns are not clearly separated. The Single Responsibility Principle , known by Robert C. Martin as the S in the acronym SOLID, is, by the way, an expression of Separation of Concerns .
Bertrand Meyer , the father of the Eiffel programming language, wrote in detail in his 1988 book Object-oriented Software Construction that methods should either provide information about the state of an object (getters), or change the state of an object (setters or mutators). He wrote: Asking a question should not change the answer and meant that a read access should not change the state of an object.
One to read, one to write
This may not sound surprising today, because consciously or unconsciously most programmers today follow this rule. There are a few exceptions, such as operations that are supposed to be indivisible (get and increment) or querying the last error message that occurred, which typically clears the error memory and thus changes the state of the object. But if the idea of separating different concerns, i.e. separating read and write accesses at the level of individual objects, is such a good (and central) idea, why don't we apply it to entire applications as well?
As a simple example, let's look at the API of an ordering service. Let us outline some methods:
interface OrderService |
{ |
public function findOrderById ( OrderId $orderId ) : Order ; |
public function findOrdersByUserId ( UserId $userId ) : Orders ; |
public function placeOrder ( |
UserId $userId , |
array $items |
) : void ; |
public function cancelOrder ( OrderId $orderId ) : void ; |
// ... |
} |
This interface contains both read and write methods. If we follow the principle of Command Query Separation (CQS) and separate the read and write methods, we get two interfaces:
interface OrderWriteService |
{ |
public function placeOrder ( |
UserId $userId , |
array $items |
) : void ; |
public function cancelOrder ( OrderId $orderId ) : void ; |
// ... |
} |
interface OrderReadService |
{ |
public function findOrderById ( OrderId $orderId ) : Order ; |
public function findOrdersByUserId ( UserId $userId ) : Orders ; |
// ... |
} |
We don't want to go into whether and how this API could be made even better, the methods only serve as an example.
You will ask yourself what the separation of reading and writing methods really does. As a first step, you might be happy about a security gain , because the application no longer needs write permissions for read access. This means less surface for SQL injections , for example, which can still lead to unauthorized reading of data at this point, but no data can possibly be changed.
Since security is (unfortunately) rarely a sales argument, we take a closer look behind the scenes of our fictitious application. We imagine that the order is represented by the following domain object:
class Order |
{ |
// ... |
public function __construct ( $userId ) |
{ |
// ... |
} |
public function addItem ( OrderItem $orderItem ) : void |
{ |
// ... |
} |
public function getTotal ( ) : Money |
{ |
// ... |
} |
public function finalize ( ) : void |
{ |
// ... |
} |
public function cancel ( ) : void |
{ |
// ... |
} |
// ... |
} |
The persistence for an order is hidden behind a repository facade:
class OrderRepository |
{ |
// ... |
public function addOrder ( Order $order ) : void |
{ |
// ... prepare to persist this order ... |
} |
public function findOrderById ( $orderId ) : Order |
{ |
$order = new Order ( $orderId ) ; |
// ... retrieve data and hydrate order ... |
return $order ; |
} |
public function findOrdersByUserId ( $userId ) : Orders |
{ |
$collection = new Orders ( ) ; |
// ... retrieve orders and add to collection ... |
return $collection ; |
} |
// ... |
} |
So we can load a single order, or a collection of orders from the repository. This collection is, if you like, a type-safe array of Order
objects. To save a new order permanently, it is simply passed to the repository by calling addOrder()
. We imagine that the persistence infrastructure then ensures that the order is stored permanently. An implementation of the OrderWriteService
interface could look like this:
class OrderWriteServiceImplementation implements OrderWriteService |
{ |
public function placeOrder ( |
UserId $userId , |
array $items |
) : void |
{ |
$order = new Order ( $userId ) ; |
foreach ( $items as $item ) { |
$orderItem = new OrderItem ; |
// ... populate order item ... |
$order -> addItem ( $orderItem ) ; |
} |
$this -> orderRepository -> addOrder ( $order ) ; |
} |
public function cancelOrder ( $orderId ) |
{ |
$order = $this -> orderRepository -> findOrderById ( $orderId ) ; |
$order -> cancel ( ) ; |
} |
} |
When creating an order in the method placeOrder()
, an order is first created, filled with the transferred line items and then transferred to the repository so that the order is (later) persisted. How and when exactly this happens is irrelevant at this point. After all, the task of a facade is to hide a complex subsystem
.
If an order is to be cancelled, the business object that represents this order must first be loaded from the repository. Then simply call the cancel()
method on this object. We ignore the fact that the code shown here does not contain any error checks, as well as the lack of additional parameters that can be used to log when which user placed or cancelled an order.
Code similar to these examples can be found in a similar form in many modern PHP applications, often based on a framework like Symfony and using an ORM solution like Doctrine . For write accesses, this may also make sense in its present form. But does it also make sense to use the same model for read accesses? Just because the read and write accesses use the same data doesn't mean they have to use the same model .
Let's consider what needs to happen in a read access if we want to use the above structures: First, the associated business object must be loaded from a suitable repository. This is done by accessing a data source, either a relational database or a document database. The ORM or ODM loads the raw data and creates PHP objects from it (we make the not unrealistic assumption that the order is an aggregate ). If we're lucky, all the information we need for the display is inside the order aggregate. If this is not the case, we need to load more business objects and repeat the whole procedure.
Now we have to retrieve the state from the business object(s) in order to have them rendered by our presentation code as HTML, or for AJAX requests as JSON or for another API request perhaps as XML. It is important to note that the view in our business object does not call any write methods, respectively that the CQS principle is really implemented cleanly and none of the read methods change the state of a business object. This can be achieved by using read-only proxies, but this is not so easy to implement when dealing with an aggregate. So it becomes apparent already here that passing domain objects to a view is not necessarily the best idea.
Reading is too much work
If the data was originally stored in a document database such as MongoDB , we have now converted the JSON-like format stored there into a PHP object graph for an AJAX request, for example, and then serialized it back to JSON. For a query from a relational database, we dynamically assembled an SQL statement that was parsed and executed by the database. The result of this query is then converted into a PHP object graph, which is serialized to XML, for example, and then output as HTML using XSLT. This all sounds like a lot of work. Is all of that really necessary?
If we follow the architectural principle Command Query Responsibility Segregation , abbreviated CQRS, the answer is a clear no. In addition to the separation of read and write accesses, the data is independently stored not only in the data memory used by the writing side, but also – in a representation optimized for read accesses – redundantly in one or more other data memories. This is not caching in the actual sense, because a cache does not guarantee that data is actually present in it.
CQRS requires that all read accesses are made to a separate, optimized representation of the data. No domain object , and therefore no ORM, is required for this. Strictly speaking, not even PHP objects are required (data without behavior does not normally justify an object). Instead, JSON or XML data, for example, could be passed directly from the data source to the client, if necessary after a check whether the respective user is actually allowed to see this data.
The data prepared for read access, the so-called projections , can be stored in a key-value store such as Redis , a document database such as MongoDB or CouchDB , a search engine such as Solr or Elasticsearch , or even a graph database such as Neo4j . It is not realistic to map completely different requirements like search, reporting and transaction processing into a single model. So why not benefit from the fact that there are different types of data stores optimized for specific use cases?
When to update projections?
A classical architecture would generate its reading projections in the reading request. This is a bad idea, because as stated above, there are at least an order of magnitude more of them than write requests. And why reassemble the same data again and laboriously to form a view if nothing has changed at all?
This would suggest creating the read projections in the write request. In simple cases this can be done, but for better scalability and performance of the write requests, you should only create an event there. This event is processed asynchronously in a separate background process, and the read projections are created. The different models for read and write access can therefore not only be calculated in separate processes, but this calculation can even be outsourced to your own hardware. We have thus created an extremely well scalable architecture that will also show very good performance from the user's point of view: Write requests are also processed quickly, as there is no need to wait for the views to be generated.
However, there is (apparently) a catch: doesn't asynchronous generation of the projections mean that changes on the writing side are not immediately visible on the reading side? Yes, that is so. All distributed systems, i.e. systems consisting of more than three computers or components, find it difficult to guarantee transactional consistency across the entire system. This consistency problem is not really new, but is inherent in every Web application: If one user retrieves data and another user changes it afterwards, the dataset is strictly speaking inconsistent from the user's point of view, because the first user sees outdated data. A CQRS architecture is also eventually consistent in this sense.
Sending Commands
The write accesses to the application are the so-called commands . A command is basically just a parameter object that is created by the client and sent to the server. We recommend that you abstract the commands from the HTTP protocol, which is relatively simple:
interface LoginCommand |
{ |
public function getUsername ( ) : string ; |
public function getPasswordHash ( ) : PasswordHash ; |
} |
class HttpLoginCommand implements LoginCommand |
{ |
private HttpRequest $httpRequest ; |
public function __construct ( HttpRequest $httpRequest ) |
{ |
$this -> httpRequest = $httpRequest ; |
} |
public function getUsername ( ) |
{ |
return $this -> httpRequest -> getParameter ( 'username' ) ; |
} |
public function getPasswordHash ( ) |
{ |
return new PasswordHash ( |
$this -> httpRequest -> getParameter ( 'passwordHash' ) |
) ; |
} |
} |
Instead of a mapper that generates a specific command from an HTTP request, we define an interface for each command and let it read the data directly from the HTTP request on demand. If required data is missing, the method getParameter()
in `HttpRequest' will throw an exception.
Complex commands can be easily implemented by a CompositeCommand
, which is composed of different commands:
class CompositeCommand |
{ |
// ... |
public function __construct ( |
LoginCommand $loginCommand , |
ConfirmEmailCommand $confirmEmailCommand |
) |
{ |
// ... |
} |
public function getUsername ( ) : string |
{ |
return $this -> loginCommand -> getUsername ( ) ; |
} |
public function getEmail ( ) : Email |
{ |
return $this -> confirmEmailCommand -> getEmail ( ) ; |
} |
// ... |
} |
The decoupling of HTTP not only makes it irrelevant which client generates a command, but also solves the problem that HTTP request objects have an implicit API . Commands, on the other hand, have an explicit interface, which makes it easy to see which parameters are actually required during processing. A command handler could look like this:
class CreateOrderCommandHandler |
{ |
public function __construct ( |
OrderWriteService $orderWriteService |
) |
{ |
// ... |
} |
public function run ( |
CreateOrderCommand $createOrderCommand |
) : void |
{ |
$userId = $createOrderCommand -> getUserID ( ) ; |
// ... retrieve items from command ... |
$this -> orderWriteService -> placeOrder ( $userId , $items ) ; |
} |
} |
Such a command handler would probably correspond to the controller in an MVC architecture. Its task within command processing is to execute a correctly parameterized call of the actual business logic.
One could also come up with the idea of writing the code from the placeOrder()
method of the OrderWriteService
implementation directly into the handler. But I personally prefer to have the entire interface of the application actually available as PHP code. The fact that the OrderReadService
or OrderWriteService
interface has to be implemented guarantees that our application supports the methods required there with the correct parameter signatures. In conjunction with the explicit interfaces of the commands and meaningfully defined value objects representing important domain concepts, the application already offers almost all the advantages of a strongly typed language despite PHP's dynamic type concept.
GET for Reading, POST for Writing
Interestingly, the CQRS idea fits the web very well by design , so to speak. The original HTTP specification states that GET requests only request information and do not change the state of the server. POST requests, on the other hand, change the status of the server. This fits in perfectly with CQRS.
The counterpart to the commands are the queries. By definition, a query does not change the state of the application. Since only data is queried, no business rules need to be enforced. So why load a business object that encapsulates data and behavior if you don't need the behavior at all at this point?
On the writing side, on the other hand, you are interested in the behavior of the business objects, but you deliberately ignore any presentation aspects. The business objects therefore need fewer getters, since no one has to query their state to create views or projections.
The idea of storing views in separate data stores does not make relational databases superfluous. As canonical databases ( single source of truth ), they can serve well by ensuring data integrity. Normal web requests, however, do not need a relational database, since they only access the read projections of the data.
Again, this is not caching. While caching in the pull-principle fetches required data from the back ends, here the data is pushed forward by the back ends in the push principle.
In accordance with the REST principles, a web application should therefore only initiate changes to the application state using POST requests. This means that commands are generated or transferred to the application using POST requests, and queries are generated or transferred using GET requests.
You can implement a strict separation of read and write accesses in Web applications, in that the response to a POST request is always a redirect. If a client retrieves an HTML page with GET that contains a form, its POST target should be a separate URL and not the URL of the page currently displayed. When retrieving a page, the application stores the current URL in the session. After the POST request has been processed, the URL is read from the session and an HTML page is generated that redirects the client to this URL. If the client is to be redirected to a different URL depending on the result of the POST processing, the URL stored in the session is simply ignored.
A clear separation between action (command) and display (query) can be implemented very well in this way, if you really want to, even within an MVC framework .
Conclusion
In classical architectures, the lack of separation of different concerns often leads to software behaving in an unpredictable way, because read accesses trigger side effects or change the state of the application. CQRS ideas, which can also be introduced step-by-step into existing software and architectures with reasonable effort, make the behavior of the application much more predictable.
However, CQRS is not a universal solution for every problem. Whether simple CRUD applications and applications without significant business logic can benefit from CQRS must be assessed on a case-by-case basis.
The more the object model and user interface differ, the more you benefit from CQRS. I often see relatively problematic code in consulting practice, which was obviously created using screen designs. It is not a good idea to create domain objects based on screen designs. On the other hand, when designing the user interface, you should not be forced to stick to business objects. If these two aspects are considered independently of each other, the result is a much better maintainable design. In a way, CQRS is the tool that makes Separation of Concerns possible.