As we explored in the two preceding projects, a mediator allows us to decouple the components of our system. The mediator is the middleman between colleagues, and it served us well in the small chatroom samples where each colleague can talk to the others without knowing how and without knowing them.Now let’s see how the Mediator pattern can help us follow the SOLID principles:

  • S: The mediator extracts the communication responsibility from colleagues.
  • O: With a mediator relaying the messages, we can create new colleagues and change the existing colleagues’ behaviors without impacting the others. If we need a new colleague, we can register one with the mediator.
  • L: N/A
  • I: The Mediator pattern divides the system into multiple small interfaces (IMediator and IColleague).
  • D: All actors of the Mediator pattern solely depend on other interfaces. We can implement a new mediator and reuse the existing colleagues’ implementations if we need new mediation behavior because of the dependency inversion.

Next, we explore CQRS, which allows us to separate commands and queries, leading to a more maintainable application. After all, all operations are queries or commands, no matter how we call them.

Implementing the CQS pattern

Command-Query Separation (CQS) is a subset of the Command Query Responsibility Segregation (CQRS) pattern.Here’s the high-level difference between the two:

  • With CQS, we divide operations into commands and queries.
  • With CQRS, we apply the concept to the system level. We separate models for reading and for writing, potentially leading to a distributed system.

In this chapter, we stick with CQS and tackle CQRS in Chapter 18, Introduction to Microservices Architecture.

Goal

The goal is to divide all operations (or requests) into two categories: commands and queries.

  • A command mutates the state of an application. For example, creating, updating, and deleting an entity are commands. In theory, a command should not return a value. In practice, they often do, especially for optimization purposes.
  • A query reads the state of the application but never changes it. For example, reading an order, reading your order history, and retrieving your user profile are queries.

Dividing operations into mutator requests (write/command) and accessor requests (read/query) creates a clear separation of concerns, leading us toward the SRP.

Design

There is no definite design for this, but for us, the flow of a command should look like the following:

 Figure 16.8: Sequence diagram representing the abstract flow of a commandFigure 16.8: Sequence diagram representing the abstract flow of a command 

The consumer creates a command object and sends it to a command handler, applying the mutation to the application. I called it Entities in this case, but it could have sent a SQL UPDATE command to a database or a web API call over HTTP; the implementation details do not matter.The concept is the same for a query, but it returns a value instead. Very importantly, the query must not change the state of the application. A query should only read data, like this:

 Figure 16.9: Sequence diagram representing the abstract flow of a queryFigure 16.9: Sequence diagram representing the abstract flow of a query 

Like the command, the consumer creates a query object and sends it to a handler, which then executes some logic to retrieve and return the requested data. We can replace Entities with anything the handler needs to query the data.Enough talk—let’s look at the CQS project.