Solid API Refactoring With Mediator & CQRS Pattern-From Chaos to Serenity

Semih Şenvardar
hesapkurdu-development
7 min readSep 14, 2020
Image Source : stonestacking.co.uk

Although API applications start as quite plain, readable, and clean in the early days, maturing processes and new requirements can make the application architecture very complex.

When neglected for a long time, usually due to the need for fast production, time constraints, and the lack of resources, it creates a technical debt that is not easy to overcome. Nobody in the team enjoys refactoring these types of overly complicated architecture.

In such cases, we can put the existing functionality in a certain order and make it more understandable with tiny touches, just as in the Jenga game. Now, let’s ground this theoretical explanation with a concrete example and remove the question marks in our minds.

Real Life Example

Let’s say we have an Asp.Net Core based API application that has various RESTful calculation services. As a result of various improvements made over time, we have an API controller that looks like this:

“Before software can be reusable, it first has to be usable.”

Ralph Johnson

There are many different service modules that return payment plans, interest rates, amortization calculations and provide caching, logging, and mapping solutions. While this example is not very busy and complex, real-life scenarios usually get much more crowded. I felt the need to keep it a little simpler for clarity.

Logging & caching controls, mapping operations, and service calls in each API method in the controller turn into blocks of code that are difficult to read and understand as time passes. In most cases, these controllers become God-classes, which is one of the main anti-patterns to avoid.

light as feather API applications

There are many different approaches to solve this situation. The one I want to focus on is applying Mediator and CQRS patterns together. This approach not only simplifies our code but also complies with the SOLID principles effectively. Before looking at the implementation, let’s try to understand these patterns, though not in-depth.

Mediator Pattern

Mediator Pattern, one of the behavioral design patterns, abstracts the communication between objects through the mediator object (encapsulation).

Objects cannot communicate with each other directly, communication between them is the responsibility of the mediator object. This situation decreases the dependence between objects and therefore coupling.
We can implement this design pattern with our own methods or we can use MediatR, a very popular, simple, unpretentious mediator implementation library in the .Net ecosystem.

CQRS (Command Query Responsibility Segregation)

The CQRS pattern, originally used by Greg Young as CQS, and which is its extension, generally defends the thesis that a method must either change the state of an object or return a value.

Command: It is what changes the state of an object or system. It does not return any value back.

Query: It does not change the state of the object or system. It just returns a value.

The reason we need CQRS in our refactoring process is to separate loads of reading and write operations so that they can be scaled independently from each other. Keeping commands and queries in separate models can present a new consistency challenge. Compliance with DDD and Event Sourcing structures and its applicability to complex domains can be listed as the pros of this pattern.

(*In architectures where simple business rules are operated and in a world where CRUD operations suffice, the use of CQRS pattern will bring unnecessary complexity. Therefore use cases need to be carefully evaluated.*)

Inspired by real-life scenarios (👼) I shared an API controller example above. Now let’s consider an example API method in that API controller.

When we consider that dozens of methods similar to this method are used even in this simplistic API controller, it shouldn’t surprise anyone when the team avoids the refactor process, ignores the situation, or resorts to good old “if it’s not broke, don’t fix it” approach. However, no matter how hard it may be, we will take this responsibility and get to work. 😄

After creating commands and queries for the API methods we have developed, the first thing we need to do is to create separate mediator handlers for these commands and queries.

We will call caching, logging, and mapping mechanisms in these handlers.

Since each mediator handler will handle a single command or query, we will satisfy the Single Responsibility Principle. In addition, we will be able to define new mediator objects without changing existing mediator handlers. Thus, we will satisfy the Open/Closed Principle. We will send commands and queries to handlers with the mediator object we created in the API controller. Handlers will process these requests and we will complete a challenging but enjoyable refactoring process.

“Programming is about managing complexity: the complexity of the problem, laid upon the complexity of the machine. Because of this complexity, most of our programming projects fail.”
Bruce Eckel, On Java 8

refactoring like thanos

We will start by installing the Mediatr library, which we will use to implement the Mediator pattern and the package that we will use for dependency injection.

Install-Package MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection

If you are developing in the .Net Core ecosystem, you can also perform your nuget package operations with dotnet cli.

dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection

Then we can start using Mediatr with the injection in Startup class.

  • Hint : In our example, we will define mediator handlers in API layer. That’s why we used Startup for injection. In multi-layered structures, the assembly in which mediator handlers are defined should be injected. In such cases, it will be sufficient to pass any handler parameter you defined to the type of method.

We can define the abstract classes that we will inherit our commands and queries from them as follows.

There are two types of IRequest objects in the Mediatr library that return values and not. We used the non-returning type for commands and the version that returns values ​​for queries. All commands and queries must implement the IRequest contract in order for each request to be transmitted by the Mediator to be processed by handlers.

Let’s first try to make the command/query separation for the API method that calculates the payment plan we gave as an example above.
If we perform operations such as adding, updating, deleting data in the database during the calculation process, or if we publish an event such as PaymentPlanCalculated when the calculation is completed and make a state change, that’s the explanation of the command. If we only perform calculations with some values ​​read from the database, we can call it a query. We will perform our example as a query, but the commands will also have a similar structure.

In the query, the parameters needed by the method in the service module are included and the return value is also specified. Now it’s time to write the handler that will handle this query. We will also move operations such as logging, caching, mapping into handler by separating them from the controller.

After this point, the only thing left will be to forward the request to the relevant API method to handler via the mediator object we will create in the controller. If we can apply this refactoring for each API method in the controller, we will have a very lightweight and cool API controller. And here is the sneak peek:

It seems that the old complex dependencies, the difficulty of reading caused by the controls developed for different needs, and the cumbersome code blocks are replaced by a philharmonic orchestra concert that relaxes the soul. And mediator object is the conductor of this orchestra.

thor loves mediator

What about our Tests ?

Now, it is necessary to add the tests for the handlers that process the requests of the mediator object in our new structure. We will add them next to the integration tests we use to test our APIs and the unit tests we use to test our services.

Since we are advancing our refactor process by considering the Open/Closed Principle, we do not need to make any changes to our existing tests. All we have to do is write our new unit tests for our new handlers. The test structure we will create for the handler in our example can be as follows.

To keep things simple, I shared a test example that contains only a happy path scenario. Test scenarios are of course much more detailed in real life. With our new structure, we now have modules that are loosely coupled and isolated. By using this flexibility, we can increase our code coverage metrics.

Conclusion

Refactor processes in projects without unit and integration tests can be very painful. In such cases, we can perform easy maintenance and testable transformations with Mediator Pattern & CQRS implementation. In doing so, we will also satisfy solid principles. The most critical point here will be to determine the requirements and the solution correctly. Sometimes, the steps taken to solve the existing complexity in the infrastructure can make the structure even more complex, contrary to what was aimed.
You can find detailed information in the references section of the article for the concepts that I have to explain superficially in some places in order not to depart from the purpose of the article.

If you have read so far, thank you for your patience and support. I hope it was useful for you.

If you want to get support or talk to us about similar issues, you can contact us at dev@hesapkurdu.com

From Istanbul,With Love !

References

--

--

Semih Şenvardar
hesapkurdu-development

just some stuff about software development, sharing is caring i guess.