dateo. Coding Blog

Coding, Tech and Developers Blog

Create your own mediator implementation (or use this one)

Dennis Frühauff on May 16th, 2023

In the article on MassTransit's mediator implementation, I claimed that building your own mediator implementation is easy homework to do. To prove that, I will outline how to do this in this month's article. But before we start, let's recap why that might be a useful thing to do.


I have several years of experience in vertically sliced applications and have seen different ways of implementing the mediator pattern, from package-based to fully-grown custom libraries. Here, I want to outline an implementation that covers the minimum feature set that I'd deem necessary. I hope to encourage you to build a similar thing and use it in your applications (if it fits the requirements of your domain). Or, you might want to check out the library that this article is based upon and use it in your code. All the code is hosted on GitHub and available as a NuGet package


But first, let's quickly recap the mediator pattern in general.


Introduction

The mediator pattern helps you build clearly separated vertical slices in your application, thereby enforcing boundaries between different use cases of your domain. The main idea is to have a request object for each major use case in your system which is simply a DTO for the actual operation that you want to execute. Once you'll hand over this request to the mediator, it will instantiate the corresponding hander implementation for this request and return the results back to you.
Oftentimes, requests are divided into two major categories like Commands and Queries. Queries can be thought of as read-only operations, like FindMyEntity or GetSortedCustomers. Queries should not alter state.
Commands, on the other hand, usually alter some state in your application and may or may not return actual data back to the caller. Typical commands might be CreateNewEntity or TakeOutTrash.
At best, requests and their handlers are completely isolated from each other, they perform calculations, call databases and process results. In that sense, they are some kind of a WET approach to the application domain. Of course, if some requests do very similar things, it is good practice to move common functionalities in shared services.


Needless to say, this problem has already been solved. There are existing implementations out there that could do those things to you. Specifically, we already took a deeper look at MassTransit's mediator implementation and there is also the famous MediatR by Jimmy Bogard. So, why would anyone solve that problem again?


Well, first of all, if you are following a clean architecture style in your project, you might be hesitant to introduce additional third-party libraries into your core domain. Commands and queries are part of your application logic, so you might want to keep external and unmanaged dependencies to a minimum.
Second, it is very unlikely that any of the existing implementations fit exactly the problems you have to solve in your domain. Usually, you have to do some customization to the library, maybe a wrapper, an additional abstraction layer or similar, to really make it do what you want. MediatR, for example, does not distinguish between queries and commands. If customization is necessary anyway, you might consider implementing the thing yourself. That way, it will always fit the problems you want to solve.
Third, it's a fun exercise to do, and you might learn a thing or two along the way.


Assuming any of this got you - let's start! But wait, what if you don't want to write a custom implementation and you don't like any of the existing libraries out there? Maybe you'll like my own, so please stay with me 😉


The exercise

Let's sum up the main requirements for a mediator implementation:


  • There is an interface IMediator, or similar, which can take requests (i.e., commands and queries) and route those to designated handlers for these requests.
  • Commands and queries can be defined via interfaces ICommand or IQuery.
  • Handlers for the above interfaces can be registered in the dependency injection framework (let's stick to ASP.NET for now).
  • Queries must return results.
  • Commands may or may not return results.
  • Exceptions thrown during the execution of the handler are routed back to the caller.
  • Ideally, the mediator implementation supports a pipeline approach for additional features, such as validation of requests, or authorization.
  • The processing of requests can be canceled.

That is pretty much all there is to it. It should get you started to create a simple web application and a project for the mediator implementation (and a test project, ideally). Have fun!


Medi8.Net

If you've read this far, I see two possibilities: a) You've tried an implementation on your own, or b) you don't want or need to give it a try but you are interested in the library that I created. Either way, let's take a look at Medi8.Net.
Based on the requirements above, it is an implementation of the mediator pattern with the minimum set of features that I deem reasonable for this sort of package. At the point of this writing, it is based on .NET 7 and can be readily used in any ASP.NET application.


After installing the corresponding NuGet package, you can add it to your code using the following setup code:


serviceCollection.AddMediator(cfg =>
{
    cfg.AddHandler<MyQuery, MyQueryHandler>();
    cfg.AddHandler<MyCommand, MyCommandHandler>();
});

and by using the interface IMediator you should be able to issue calls like the following:


var result = await this.mediator.HandleQueryAsync<MyQuery, MyResult?>(new MyQuery(...), CancellationToken.None);

The corresponding commands or queries are implemented like this:


public record MyQuery(string Name) : IQuery<MyResult?>
{
    public class MyQueryHandler : IQueryHandler<MyQuery, MyResult?>
    {
        public Task<MyResult?> HandleAsync(ProcessingContext<MyQuery, MyResult?> context)
        {
            // perform the actual handling of the query and return the result
        }
    }
}

There is a full sample application that demonstrates the basic usage in an executable example.


Medi8.Net supports registration of middlewares to further process your requests and provides support for asynchronous validation out-of-the-box.


Now, you might ask yourself: Do we need another library for this? And yes, maybe we don't. But then again, maybe someone actually does find the approach useful and favors a very lightweight implementation that has almost no dependencies.


Since it is in an early stage, chances are that you might find an issue. If so, or if you feel an important feature is missing, feel free to raise an issue in the repository and help me make it better.


Conclusion

From my own experience, creating a custom mediator implementation really is a fun exercise. I hope you learned a thing or two during the process. If you checked out Medi8.Net I invite you to leave some feedback to make it better in the future!



Please share on social media, stay in touch via the contact form, and subscribe to our post newsletter!

Be the first to know when a new post was released

We don’t spam!
Read our Privacy Policy for more info.

We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept All”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent.