1

I've just started in a new job and the company use Mediatr to handle all requests in their ASP.Net controllers.

I just can't see the benefit in using this pattern. I can see negatives but I'm struggling to see ANY positives or even understand what problem it's meant to be solving.

They are using the CQRS pattern, which I can understand why you might want to do this. I'm not totally convinced it gives a real world advantage but I can at least understand the theory. It's just the Mediatr part that confuses me.

Why would I want to use Mediatr instead of just calling the commands directly from the controller using DI to inject the required command objects into the actions.

The obvious negatives I can see are:

  1. Extra dependency (Mediatr itself)
  2. The commands, controllers, etc. are tightly coupled to Mediatr (they won't work without it)
  3. Extra cognitive load - you need to know what Mediatr is doing / where the commands are that are being called by Mediatr to do the work, instead of just pressing F12 on the reference in the controller.
  4. More code - using Mediatr requires writing Mediator specific code (at minimum types to derive from and references to them so that Mediatr knows which classes are handlers).
  5. Performance overhead - This is probably small, but it is an overhead compared to just a simple method call.

I've spent a few hours googling / reading on here and read lots of people saying that it keeps the controller actions small, but does having a rule that you must use Mediatr really make the actions any smaller than a rule that you must put all the logic in a separate command class / service class and just call a single method? The controller pattern itself, when implemented properly, requires that you should only have presentation logic (plus a call to the business logic) in the controllers and this is still needed when using Mediatr.

The codebase is using old ASP.Net 4.7 (not core) and DI through AutoFac, in case this makes a difference. I'm used to using .Net 7/8 and it may be that this pattern is to cover up limitations that were solved in newer .Net and I'm failing to see for this reason.

I expect that I'm missing something; I'm not saying Mediatr is crap, just that I don't understand what's good. I'm trying to learn, not start an argument.

The mediator pattern seems to be designed to solve the problem of lots of components all calling each other and having dependencies on each other so you have a mediator to coordinate things but in ASP.Net it has the MVC pattern where there is only one entry point (the controller) and the problem doesn't seem to exist.

Simplified example:

public class WidgetController
{
    private readonly IMediator _mediator;

    public WidgetController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IHttpActionResult> Get(GetWidgetRequest request)
    {
        var response = await _mediator
            .Send(request)
            .ConfigureAwait(false);
        return Json(response);
    }
}

public class GetWidgetRequest : IRequest<GetWidgetResponse>
{
    ...
}

public class GetWidgetHandler : IRequestHandler<GetWidgetRequest, GetWidgetResponse>
{
    public async Task<GetWidgetResponse> Handle(GetWidgetRequest request, CancellationToken cancellationToken)
    {
        return await GetResponseAsync();
    }
}

public class GetWidgetResponse
{
    ...
}
6
  • 1
    Some of the guys at work are doing similar things, I agree, it seems to add a useless layer as you still need the controllers. Could you add example code to your question so we can see exactly what you mean though
    – Ewan
    Commented Sep 16 at 9:43
  • 1
    Are all readers supposed to know which package named "Mediatr" you are referring to? Adding link would be really nice, thanks!
    – Doc Brown
    Commented Sep 16 at 12:33
  • ... also, what did your coworkers say when you asked them this question?
    – Doc Brown
    Commented Sep 16 at 12:35
  • @DocBrown TBH, if anyone doesn't know about the Mediatr package already, they're probably not the right person to be answering the question.
    – Mog0
    Commented Sep 17 at 7:37
  • 3
    "if anyone doesn't know about the Mediatr package already, they're probably not the right person to be answering the question" - that's the usual excuse by people too lazy to add a link. Don't forget, on this site, every question does not address only potential answerers, but many more people who simply want to read and learn something.
    – Doc Brown
    Commented Sep 17 at 9:03

2 Answers 2

2

Be very careful about the distinction between the mediator pattern and Mediatr, the library that provides tools to implement a version of the mediator pattern. In this answer, take note of when I used "mediator" and when I used "Mediatr".

TL;DR
I get the feeling that your judgment of Mediatr (and the mediator pattern) is based on some inflated expectation of how good you think it should be, and then judging it harshly on failing to meet that inflated expectation. If you look at the Mediatr GitHub page, it signals that its goals are much smaller than what you're trying to judge it by:

Simple, unambitious mediator implementation in .NET

And that's really different from what you're bringing up here as your concerns.


  1. Extra dependency (Mediatr itself)

If you're talking about package dependency, then package count is not a meaningful metric. Less is not inherently better. There is an added cost to adding packages to a codebase, but in turn they bring something to the table. The implicit point being made here is that packages should only be added if they add more than they subtract, at which point they're a net positive.

If you're talking about injected dependencies, i.e. as part of your "instead of just calling the commands directly" mention in the question, then the direct answer is that you only need to inject one mediator, instead of having to inject several different handlers each.
Sure, if your class only calls one handler, then replacing that one injected handler with an injected mediator is a neutral operation in terms of reducing the number of injected dependencies, but it still isn't a negative like you're claiming.

  1. The commands, controllers, etc. are tightly coupled to Mediatr (they won't work without it)

There's nothing stopping you from calling command/query handlers directly, but then you're not using the mediator pattern. Either you have a mediator, or you don't. The concept of having commands/queries and distinct handlers is separate from whether you access them via a mediator or not.

If your concern is that the injected mediator dependency is an interface that comes from the Mediatr library, and you would prefer for it to be a homebrew interface; nothing's stopping you from creating that.
But that's a different question. That's not "why use a mediator pattern?", that's more "should we always write full abstraction layers around every library we ever use?".

The answer to that latter question often ends up being a pragmatic one. Some libraries are considered to be external helpers that are a dime a dozen; at which point abstraction is very much desired. Other libraries are so foundational and trustworthy that the effort of writing a wrapper around them far outweighs the potential benefits, to the point where you might (a) not be able to efficiently write the wrapper without copy/pasting the library structure anyway and (b) not actually move away from the library and leverage that abstraction that you spent so long creating.

Often, we call these latter libraries "frameworks" to indicate that they are considered part of the backbone of the application, to the point where you can't meaningfully wrap them in an abstract wrapper.
Mediatr is often treated like a framework here, which is why devs tend not to get caught up on the fact that they're injecting a Mediatr interface as opposed to a mediator interface that they wrote on your own.

That doesn't mean you can't/shouldn't do it, but it might be a colossal waste of effort with a pay off that you never benefit from.

  1. Extra cognitive load - you need to know what Mediatr is doing / where the commands are that are being called by Mediatr to do the work, instead of just pressing F12 on the reference in the controller.

The separation of command and handler is intentional, specifically to avoid tight coupling between the two. There can be more than one handler. Not at the same time, but in the codebase; and your startup config might be composing a different set of handlers based on e.g. your user token or some kind of external configuration setting, or you might be relying on Mediatr's ability to decide handler priority based on relative assembly location.

In the cases where I don't have an expectation of multiple handlers, I make the handler a nested class of the command/query. I.e.

public class MyCommand
{
    public int MyField { get; init; }

    public class MyCommandHandler : IHandler<MyCommand>
    {
        public async Task Handle(MyCommand command)
        {
            // ...
        }
    }
}

This means you can still just as easily look up the handling logic based on the command, or vice versa.

The "extra cognitive load" argument is often a facile one. This tends to indicate that you're not familiar with something yet. Yes, it's technically correct that needing to learn something new introduces an additional cognitive load, but we should not be judging the introduction of a library solely by people who have never worked with it.

More importantly, we should judge whether using the library (when already knowing how to use it) request more/less/the same effort as doing the same thing in homebrew code without relying on the library.

Mediatr, at its very core, acts like a DI container, except that it looks handler (= "dependency") based on an input param type (i.e. your command/query). If that's all you use it for, yeah you could probably whip up something like that on your own in an hour or two, if you're comfortable with generics and either reflection or manually registering all handlers.

However, Mediatr has a boatload of additional features, such as DI container integration (for injected dependencies into your handlers), streams, notifications (i.e. multiple handlers for one notification), configurable pipeline steps, ...
Do you have to use those features? No. But it's not the library's fault for you not using the tools that it provides, nor should we conclude that the library should be avoided just because your personal use case might be a trivial one.

  1. More code - using Mediatr requires writing Mediator specific code (at minimum types to derive from and references to them so that Mediatr knows which classes are handlers).

Again we strike on the difference between CQRS and the mediator pattern. While they are a match made in heaven, they are two separate things.

The only thing that Mediatr (and mediators in general) require is that the handler uses a marker interface that indicates what it handles. Even that's not a hard requirement for the mediator pattern in general, but if you don't configure it via reflection, then the logical consequence is that you have to explicitly list which command/query goes to which handler.
I mean, technically you could even skip that and just do it using reflection at runtime, but that's not particularly easier. That's a sort of laziness that requires a more complicated implementation, which is counterintuitive.

  1. Performance overhead - This is probably small, but it is an overhead compared to just a simple method call.

As long as you don't resolve this using reflection on the fly, the performance overhead is so negligible that it probably took you more time to write that mention than the Mediatr overhead is ever going to cost you (as a life total).


but does having a rule that you must use Mediatr really make the actions any smaller than a rule that you must put all the logic in a separate command class / service class and just call a single method?

Well, no, but that's like saying "do I really need to buy a car if I design my own mode of transportation?"

Cars are just one tool to get you from point A to point B. A lot of people like being able to buy a car and not having to design one from scratch. Maybe you're not one of those people. That's fine, but please don't extend your personal observations as if they should apply to everyone else.

The controller pattern itself, when implemented properly, requires [..]

Cart before horse. "when implemented properly" entails designing your code with the appropriate patterns for the behavior you want, which can include making use of the mediator pattern to connect one component to another.

The mediator pattern seems to be designed to solve the problem of lots of components all calling each other and having dependencies on each other so you have a mediator to coordinate things but in ASP.Net it has the MVC pattern where there is only one entry point (the controller) and the problem doesn't seem to exist.

First of all, it is not recommended to daisy-chain commands (or queries) through Mediatr. While technically possible to do so, since each handler can in and of itself be a consumer of the mediator at the same time, this just enables a spaghetti codebase, sidestepping the organizational problem that the mediator problem is trying to help you solve, instead of actually solving it.

When you start daisy chaining, you get into several potential hard to catch issues like recursive pipeline steps that are being executed too many times, or conditionally infinitely recursing patterns which deadlock your application with no compiler warning whatsoever (only to be detected during runtime).

As to your observation about controllers, it's mostly irrelevant. The flow of control should generally be one-way, not "everyone calling everyone else". Whether the consumer of a mediator is a controller or any other kind of class is an irrelevant distinction.


I get the feeling that your judgment of Mediatr (and the mediator pattern) is based on some inflated expectation of how good you think it should be, and then judging it harshly on failing to meet that inflated expectation. If you look at the Mediatr GitHub page, it signals that its goals are much smaller than what you're trying to judge it by:

Simple, unambitious mediator implementation in .NET

And that's really different from what you're bringing up here as your concerns.

2
  • Thanks for your response @Flater. To clarify, I wasn't saying that performance / cognitive load / extra code were big, just that even for a small increase, you need to a positive reason for that and I was struggling to see ANY positive to it. I'm starting to see some benefits now, and while I'm still not sold on it, I recognise that some of that will be inertia given I've been writing ASP.Net apps for more than 10 years without it. I'm not someone who'll dismiss something immediately, I want to understand why someone chose that pattern and use it for a decent period before making a judgement.
    – Mog0
    Commented Sep 17 at 7:35
  • @Mog0: if your organization has many applications and projects, developer familiarity and consistency in developer experience can be a big driver. I see a common misunderstanding from developers that you need to use X% of a framework's features to make it worthwhile. In my experience, the main benefit is often developer productivity and consistency in architecture when people need to bounce between projects. Commented Sep 23 at 14:59
0

Rather than focus on the properties on the code level, you should look at it on the conceptual level. Mediatr provides a message bus with pub/sub functionality. This decouples the traditional "caller" and "callee", who no longer need to know about eachother's existence. The message is king.

Your question is not why Mediatr, it is why message bus and why pub/sub. The answer is because it solves an array of issues in a complex, potentially distributed domain. It is basically telephony versus postal service.

With the proper terms for the paradigm you can look up resources that explain the concept.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.