In previous posts, we discussed about the CQRS pattern and its advantages. I have not given any source code examples yet on CQRS because I wanted to discuss some other commonly used patterns and mediator is one of them.
MediatR is a powerful library in the .NET ecosystem that simplifies in-process messaging, promoting the mediator design pattern. It helps developers decouple components in their applications, leading to improved maintainability and testability. This article explores how to integrate MediatR with a .NET API and discusses its advantages.
What is Mediator Pattern ?
The mediator design pattern is one of the twenty-three well-known design patterns that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, and reuse.
According to suggestions from this pattern, there are two important points to note here:
- Tight coupling between a set of interacting objects should be avoided.
- It should be possible to change the interaction between a set of objects independently.
You can read more about this pattern on wikipedia.
This is achieved by implementing a mediator object. MediatR library provides the mediator object implementation. We can configure new implementations / objects and MediatR will take care of invoking the right implementation. Before we dive into details of how this is done, let’s also talk about pros and cons of this approach.
What are its advantages ?
Important advantages of mediator pattern is – decoupling. The objects interacting with each other are not tightly coupled. This helps a lot in making application more maintainable. Below are some more advantages of the pattern (using MediatR library).
Encapsulation and Decoupling
MediatR enforces the separation of concerns by decoupling the request and handler logic. This reduces dependencies between components, making the codebase easier to maintain and extend.
Improved Testability
Since MediatR promotes loosely coupled architecture, unit testing becomes more straightforward. Handlers can be tested in isolation without requiring controller dependencies.
Centralized Request Processing
MediatR allows the implementation of cross-cutting concerns, such as logging, validation, and authorization, through behaviors that act as middleware for requests.
Cleaner Controller Code
By offloading business logic to request handlers, controllers become slimmer and more focused on handling HTTP requests, improving code readability and maintainability.
Extensibility with Pipelines
MediatR supports pipeline behaviors, which enable functionalities like request pre-processing and post-processing. This is beneficial for adding logging, caching, and performance tracking.
What are possible cons?
Like any other design pattern, this is not a sliver bullet. Adopting this pattern without giving much thought may also bring some negative outcomes along with it.
Increased Complexity
While MediatR promotes decoupling, it can introduce additional layers of abstraction, making the system harder to understand, especially for new developers. Also it may not be desirable for very simple applications.
Potential Performance Overhead
Using MediatR for every interaction may introduce performance overhead due to additional method calls and object instantiations. This could be a concern for high-performance applications with a large number of requests.
Debugging and Tracing Challenges
Since requests are processed indirectly via handlers, debugging can be more challenging compared to directly invoking service methods. Proper logging and tracing mechanisms must be in place.
Overuse for Simple Scenarios
Applying the mediator pattern in simple applications or microservices with minimal dependencies can add unnecessary complexity without significant benefits.
Learning Curve
Developers unfamiliar with the mediator pattern and MediatR may need time to understand its implementation and best practices, leading to a temporary productivity slowdown.
Setting Up MediatR in a .NET API
Let’s now understand how this library MediatR can be used with .NET APIs.
Step 1: Install MediatR Packages
To use MediatR in your .NET API, install the MediatR package. This package should bring other required dependencies as shown in the snapshot given below.
Step 2: Configure MediatR
Register MediatR in the Program.cs file (for .NET 6 and later). If you are also looking at other resources parallely, note that the AddMediator call is changed after package version 12 and it needs different parameters.
Step 3: Create a Request and Handler
Now, let’s define a query and query handler. Here we are going to create a simple query class which has only one parameter “City”. This query can be handled by a query handler, which takes query as input parameter and returns the result in form of a string. Both classes are given below.
Step 4: Inject MediatR into a Controller
Now, we need to inject the mediator object in the API controller. Then this object can be used to send query and mediator will automatically pickup the query handler for it to process the request.
The code given below shows the controller after this modification is done.
Now, if we run application, we should be able to see the hardcoded string in the response as expected. As we can see, the query object and query handler objects are not interacting with each other directly. The mediator instance drives the interaction.
Conclusion
MediatR is a valuable tool for .NET API development, helping developers build cleaner, maintainable, and testable applications. Also remember that this pattern is not silver bullet.
This article is not to convince you to use this pattern in all of your projects. I think we need to be pragmatic and think about the pros vs cons on case by case basis. For simple projects, this may prove to be overkill and may seem to reduce productivity. But for very complex implementations, this pattern would help in keeping the execution flows independent and decoupled from one another, thus making application more maintainable.
Have you already been using MediatR library or mediator pattern in your application ? What are your thoughts ?
