Alex KlausDeveloper  |  Architect  |  Leader
Is Mediator/MediatR still cool?
01 January 2020

Don't ruin the Mediator/MediatR

Arguably, the Mediator pattern has become cool among the .NET devs when Jimmy Bogard released MediatR library 4 years ago. That library made words ”Mediator” and ”MediatR” interchangeable in the devs’ lexicon. And as usual, trendy tools get often misused and applied without a second thought (because ”C’mon, it’s MediatR!”).

BTW, there is also Brighter that is a bit less mainstream and very similar to MediatR in terms of the API but has support of out-of-process request handlers by using a Message Broker like RabbitMQ.

Here I’m playing devil’s advocate and look closely at its pros and cons. Is it still beneficial for modern .NET projects to bring the Mediator (MediatR / Brighter)?

What problems Mediator solves?

The Mediator pattern defines an object that encapsulates how a set of objects interacts. It solves 2 problems:

  1. Tight coupling between interacting objects.
  2. Changing the interaction between objects independently.

It’s achieved by adding a separate object (mediator) and delegating the interaction to it. So for interacting objects A and B, where A triggers an action and B acts on it, B wouldn’t know what triggers the action and A wouldn’t know what happens when the action is triggered.

A simplified C# example would be:

public class ClassA
{
	private IMediator _mediator;

	public ClassA (IMediator mediator)
	{
		// ...
	}
	public void Process()
	{
		// Interaction with another object via Mediator
		_mediator.Send(new Ping());
	}
}

public class ClassB : IRequestHandler<Ping>
{
	public void Handle(Ping request)
	{
		// Act on command/requests
	}
}

Explicit Dependencies Principle

The code sample above highlights the main problem of the Mediator — violation of the Explicit Dependencies Principle, where the class should clearly indicate what it needs in order to function. This would make your code more self-documenting and your coding contracts more user-friendly.

Hiding dependencies in Mediator creates room for problems like:

  • Run-time errors on execution of a method.

    E.g. on sending a request with no or broken handler. Alternatively, for injected dependencies it could be a run-time error on creating a class, which is still better as it fails sooner.

  • Harder to trace dependencies.

    Devs maintaining the code have to understand the naming conventions, how the call stack for request handlers looks like, etc. in order to resolve dependencies. It may increase maintenance costs.

These problems may look familiar. Remember the Service Locator, which was cool in 2004 and later, in 2010, became an anti-pattern due to similar reasons? Will the Mediator follow this path? Time will tell.

Solving problems in .NET without Mediator

Dependency injection

.NET devs already use DI/IoC and the implementation of interacting objects is decoupled, as the objects refer to the contracts (interfaces).

public class MyClass
{
	public MyClass (IFoo foo, IBoo boo, IMoo moo)
	{
		...
	}
}

Tight coupling is not that acute issue in the .NET. Of course, the coupling can be looser after adding an extra abstraction layer (e.g. Mediator), but the code above meets the Explicit Dependencies Principle.

Another bonus — constructor injection makes violations of the Single Responsibility Principle glaringly obvious. Having too many parameters in a constructor is usually a red flag indicating a need to refactor.

The DI allows changing the interaction between objects almost at the same level of independence as the Mediator does. Here you trade dependency on commands for dependency on interface methods.

DI constructor madness in controllers

Perhaps, we can reduce number of constructor parameters almost everywhere in the .NET project apart from ASP.NET controllers. We craft controllers to reflect the route, which needs to be intuitive for the consumer. It may lead to bundling up way too many services and repositories to be injected in the constructor.

public DashboardController(
    IProductQueryService productQueryService,
    IRelatedProductQueryService relatedProductQueryService,
    IOrderQueryService orderQueryService,
    IOrderHistoryQueryService orderHistoryQueryService,
    ICustomerQueryService customerQueryService,
    IUserProfileQueryService userProfileQueryService,
    ISupportService supportService,
    ILogger logger
)

In addition to that, controller methods often just call a single method of an injected class. It adds overhead for WebAPI requests, where on each call the controller has to construct too many unnecessary objects.

Fortunately, the .NET provides FromServices, allowing injecting a service directly into an action method without using constructor injection. It makes controllers quite concise.

[HttpGet]
public Task<IActionResult> CurrentTime([FromServices] IDateTime dateTime)
{
    var time = $"Current server time: {dateTime.Now}";
    return Ok(time);
}

Though, FromServices attribute can be used everywhere in the project, it’s hard to justify its existence anywhere apart from controllers.

I reckon, the above shows that a reasonably clean implementation can be achieved in .NET without involving the Mediator.

What does the Mediator have to do with CQRS?

Some devs believe that a proper CQRS can be implemented only with the Mediator.

Well… Not really. They are quite independent concepts. You definitely can use a Mediator with CQRS, and probably a half-dozen other useful software patterns with both.

If interested in a detailed comparison of CQRS and Mediator with diagrams, check out this post.

What Jimmy Bogard says about MediatR?

Jimmy Bogard, the author of MediatR, has been working in consulting for many years and crafted MediatR to his needs. He blogs a lot about it at jimmybogard.com and speaks at various conferences. In this video (NDC Sydney 2016) he outlined his reasoning behind bringing the MediatR into his projects:

My basic philosophy was I will not introduce any sort of pattern or any sort of structure into my system until the code tells me that it’s needed.

He, as many other consultants, had to develop a solution while many business processes and domain entities/aggregates are still being defined. In this case MediatR does help to create granular operations with nice decoupling.

The story would be different for product-oriented teams, where understanding the domain and reflecting it in the code is paramount.

When to use Mediator in .NET?

No doubt that the code can be well-structured and easy to read/maintain without the Mediator (meeting the SOLID principles, implementing CQRS, Clean architecture, etc.). As an example, check out my YABT project.

However, there are cases for using the Mediator:

1. Complex objects interaction

When you do need to trigger dozens of commands and notifications, then doing so with just DI/IoC would lead to ”constructor madness” (dozens parameters for all the interfaces executing commands and notifications).

As I stated above:

seeing excessive dependencies should incentivise rethinking of the dependencies

But if nothing can be done, then the Mediator will come to the rescue.

2. Need in MediatR / Brighter features

Nowadays, MediatR supplies out-of-the-box commands/requests, notifications and behaviours, which come in handy for building pipelines (e.g. for validations). Brighter has out-of-process handling along with support of retries and circuit breaker and more.

If adoption of a pure Mediator is not beneficial for the project, then the whole MediatR / Brighter bundle can win you over.

3. Development where a lot remains unknown (consulting)

Where business processes and domain entities/aggregates remain murky and you need to deliver the code, then MediatR does help in creating granular operations and decoupling the code.

It can be quite common among consultants. And there are consulting companies out there that have mastered the MediatR approach to a high degree with all conventions, test practices, rules, etc. in place. It does require some supervision and upfront investment for the team, but then the team may benefit from using it.

Check out Clean Architecture project from Jason Taylor as one of examples with a neat adoption of MediatR.

Conclusion

Don’t use a blanket rule. Mediator is just a tool, so use it where applicable.

If you start a new project then attempt to structure the logic without the Mediator first. Then gradually bring the Mediator pattern where needed by leveraging the existing rich capabilities of the .NET. Once you see a need in a broader functionality then bring what’s required from MediatR / Brighter.

Do you know other examples where the Mediator/MediatR is a good fit? Other thoughts? Have your say in the comments below, on Reddit thread or on Twitter.