Mediator Pattern in C#
CQRS Pattern
CQRS stands for Command Query Responsibility Segregation. It segregates the read and write operations on a data store into queries and commands respectively. Queries perform read operations and return the DTOs without modifying any data. Commands perform writes, updates, or delete operations on the data. This distinction helps manage data access complexity in large applications by making them more decoupled and testable. Moreover, this distinction also helps to scale the read and write operations independent of each other to match skewed workloads and have separate data schemes optimized for each operation type. However, applications with simple data access requirements might not benefit from CQRS as it may add additional complexity
The Mediator design pattern is a behavioural pattern that defines an object, the mediator, to centralize communication between various components or objects in a system. This promotes loose coupling by preventing direct interactions between components, instead of having them communicate through the mediator, facilitating better maintainability and flexibility in the system architecture.
What is the Mediator Design Pattern?
According to the Gang of Four definitions, define an object that encapsulates how a set of objects interact with each other. Mediator promotes loose coupling by keeping objects from explicitly referring to each other and letting you vary their interaction independently.
The Mediator Design Pattern reduces the communication complexity between multiple objects. This design pattern provides a mediator object, which will be responsible for handling all the communication complexities between different objects.
The Mediator Design Pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object. This pattern is used to centralize complex communications and control between related objects in a system. The Mediator object acts as the communication center for all objects. That means when an object needs to communicate with another object, it does not call the other object directly. Instead, it calls the mediator object, and it is the responsibility of the mediator object to route the message to the destination object.
Let us understand the Mediator Design Pattern in C# with an Example. Please have a look at the following diagram. As shown in the diagram below, we have four objects (Object A, Object B, Object C, and Object D). And these four objects want to communicate with each other. Suppose Object A wants to communicate with Object B, then Object A should know the reference of Object B, and using that reference, Object A can call the method of Object B. Similarly if Object B wants to send some message to Object C. It should know the reference of Object C, and using that reference, it will call the methods of Object C and send the message.
The couplings between the objects are more, i.e., tight coupling. A lot of object knows each other. Now, in the above image, four objects are there. In real-time, you may have thousands of objects, and those thousands of objects want to communicate with each other. Then, writing code and maintaining code is very difficult.
Using the Mediator Design Pattern, we can reduce the coupling between objects. To understand this, please have a look at the following diagram. As shown in the image below, here we introduce the Mediator object. Each object has to send messages to the mediator object. The mediator object here will receive the message from each object and route that message to the destination object. So, the mediator object will take care of handling the messages. So, in this scenario, we can use the Mediator Design Pattern.
Please have a look at the following diagram. Nowadays, everybody is aware of Facebook. On Facebook, we can create some specific groups; in those groups, people can join and share their knowledge. On Facebook, there is a group called Dot Net Tutorials, and in that group, many people (such as Ram, Sam, Pam, Dave, Steve, Anurag, etc.) are joined. Ram is sharing some messages in the Dot Net Tutorials group. Then, what this Facebook Group (Dot Net Tutorials) will do is it will send that message to all the members who have joined this group. So, here, the Facebook group is acting as a Mediator.
Mediator is used to reduce communication complexity between multiple objects or classes. This pattern provides a mediator class which normally handles all the communications between different classes and supports easy maintenance of the code by loose coupling. Mediator pattern falls under behavioural pattern category.
Basically, a Mediator perform 2 operations.
· · Accept the incoming request
· · Handles the incoming request and provide the response.
CQRS + Mediator Patterns are preferred over large projects.
Let’s get into the code and see how this CQRS & Mediator patterns work together.
Read data using CQRS & MediatR
Step 1
Open Visual studio 2019. Create an ASP.Net Core Web API project named EmployeeManagement.API. I have given the solution name as EmployeeManagementDemo. (You can give any name of your choice).
Step 2
Make sure that you select the .Net 5.0 as target framework and enable the Open API support for Swagger and click create.
Step 3
Add a new class library project named EmployeeManagementLibrary.
Select the target framework as .Net 5.0
Step 4
Delete the class1.cs file.
Step 5
Add folders named Data & Models. In the Models folder, add a class named EmployeeModel with three properties named Id, FirstName & LastName.
Step 6
Create a class named DataAccess and an interface named IDataAccess in the Data folder. In this example I am using an in-memory collection for storing data. If you want to try it out with any DB, you may add the logic to perform DB operations.
Step 7
Add two methods named GetEmployees & AddEmployees to the IDataAccess interface as shown in figure below.
Step 8
Let DataAccess class inherit from IDataAccess interface and implement the interface as shown in figure below.
Step 9
Now let’s add the MediatR nuget package to EmployeeManagementLibrary project. Browse for MediatR nuget package in install it.
At the time of writing this article the latest stable version of MediatR is 9.0.0.
Step 10
Add two new folders named Queries & Handlers in EmployeeManagementLibrary project.
Step 11
Add a class named GetEmployeeListQuery in the Queries folder. I am using records (C# 9 feature) instead of a class. If you are not familiar with records, I highly recommend you to go through it. Let us implement the IRequest interface of MediatR package as shown in figure below.
IRequest is a generic interface. We need to specify the return type of the request. In our case I have specified List<EmployeeModel> as I need to return all employees.
Step 12
We need to handle this request. For that let us add a handler class in Handlers folder. In our case I have added a class named GetEmployeeListHandler.
Step 13
GetEmployeeListHandler implements the IRequestHandler interface of MediatR package as shown in figure below.
IRequestHandler is a generic interface. So what do you mean by IRequestHandler<GetEmployeeListQuery,List<EmployeeModel>? It means that if the request is of type GetEmployeeListQuery, then return List<EmployeeModel>.
Step 14
Inject the IDataAccess in the GetEmployeeListHandler class and apply the logic to return all employees in the Handle method.
Step 15
Now let us go to the API project and configure the Startup services. Add the reference of EmployeeManagementLibrary.
Step 16
We need to configure the IDataAccess and the MediatR in the API project. Install the nuget package named MediatR.Extensions.Microsoft.DependencyInjection.
Step 17
Once the installation is complete, open the Startup.cs class file and configure the IDataAccess and MediatR in the dependency container.
I have added the MediatR to the services collection which can access all classes in EmployeeManagementLibrary project.
Step 18
Now let us create an API controller named EmployeesController. Inject the IMediator interface in the constructor and add logic to fetch all employees as shown in figure below.
Invoke the Send method of IMediator interface. The send method accepts a type of IRequest. In our case GetEmployeeListQuery is a type of IRequest.
Step 19
So, what happens behind the scenes is that when we invoke the Send method with the request as GetEmployeeListQuery, the mediator will invoke the handler which accept this request. In our case it will go and call the GetEmployeeListHandler class and returns the response as List<EmployeeModel>.
Step 20
Now run the API project. Since we have added the open API support, the swagger page will be displayed for testing our end points. Click on the api/Employees get method.
Step 21
Now let us see how to retrieve an employee by Id.
Step 22
As we did before, add a new query named GetEmployeeByIdQuery under the Queries folder in EmployeeManagementLibrary project. This time the query accepts a parameter named Id of type int and the response is of type EmployeeModel.
Step 23
Now let’s add a handler for this request as shown in figure below.
In this handler, instead of injecting the IDataAccess, I have injected the IMediator interface in the constructor. In the handle method, using the injected interface, I am trying to get all the employees by passing the GetEmployeeListQuery object and then filtering the employee with the Id passed as input. Here you can see that I am able to access the id passed as input using the request parameter.
In a real scenario we will be injecting the IDataAccess to retrieve the employee by Id from a database.
Step 24
Go to the API project -> Open EmployeesController and add a http get method which accept an integer parameter as shown below.
Note that I am passing the GetEmployeeByIdQuery(id)as the request. Run the API project and click on the Get button of api/Employees/{id}. Pass an input id=1 and verify the output.
I hope you all understood how to use the Mediator & CQRS pattern in reading data
Write Data using CQRS & MediatR
Step 1
Till now all Read requests are kept inside a Queries folder. All reads are treated as queries.
Step 2
All other CRUD operations except Read are kept under a Commands folder. Create a Commands folder in EmployeeManagementLibrary project.
Step 3
Create, Update & Delete operations are treated as Commands.
Step 4
Add a new record named AddEmployeeCommand which takes two parameters named FirstName and LastName. Make sure that this record inherits the IRequest interface of MediatR package as shown below. So, whenever the user request for AddEmployeeCommand, it returns an EmployeeModel.
Step 5
Create a handler for AddEmployeeCommand request. Add a class named AddEmployeeHandler in the Handlers folder.
The IDataAccess interface is injected in the constructor. Implement the Handle method of IRequestHandler<AddEmployeeCommand,EmployeeModel>. The FirstName and LastName inputs can be accessed from the request parameter.
Step 6
Open the EmployeesController of API project and include a Post method to add a new employee as shown below.
Step 7
Now run the API project and execute the post method in swagger UI by passing the following inputs and verify the ouput.
The response is a new employee with id=3
This Way Mediator Pattern Works.
In conclusion, CQRS and Mediator patterns are very effective in managing the data access complexity of applications and can help in writing code that is more testable and manageable. It comes with its own set of drawbacks like inability to scaffold models using ORM tools and set of additional classes required to segregate data models and read/write operations. Hence decision to use this pattern depends largely on the size, scale and complexity of the application and its related data access patterns.
Take the first step towards data-led growth by partnering with MSA Infotech. Whether you seek tailored solutions or expert consultation, we are here to help you harness the power of data for your business. Contact us today and let’s embark on this transformative data adventure together. Get a free consultation today!
We utilize data to transform ourselves, our clients, and the world.
Partnership with leading data platforms and certified talents