You Probably Don't Need to Worry About MediatR

I've been pointed at this post from various sources, and I thought I'd take the time to address the criticisms one by one. Not because I personally care too much, MediatR evolved over many years to solve the problems my teams faced, but since others seem to care, here we go.

MediatR does not implement the Mediator Pattern

I guess? Maybe? I don't see it as an in-process bus, so if it doesn't match the Mediator pattern, then it doesn't match any currently named pattern. It matches the problem description (reducing chaotic dependencies), the implementation doesn't exactly match, but I see that also a side effect of the GoF patterns all defined before heavy use of generics. A strict application of the GoF pattern as described would have had use in maybe...one instance in my 20 year career? So I'm far less concerned about EXACT implementations of any pattern, the variations on the themes are far more interesting.

Most of the time it’s used like a glorified Service Locator, which is notoriously an anti-pattern.

Actually, no, it's never used as a service locator. No one uses the IMediator interface to locate services. They use it to dispatch requests and notifications to handlers.

Also, Service Locator is NOT an anti-pattern, as I've shown in a previous post. Strict adherence to ONLY resolving dependencies via constructor injection can lead to subjectively sub-par design and objectively horrible performance. Calling any one pattern an absolute anti-pattern I find silly anyway, there's nuance in every decision and tradeoffs abound.

[Clients when using it wind up] violating the Interface Segregation Principle

I don't understand this explanation whatsoever. When you're using a Mediator, you're not introducing dependencies on ALL handlers. Whatever problem this section describes, I've never seen it in the 12+ years I've used MediatR.

[Violates] the Explicit Dependencies Principle

This is a tradeoff. I started with explicit dependencies on individual handlers and moved away from it because of the limitations that has. You have to lean on heavy use of DI container features which I recommend my teams NOT do, because of the magic that introduces. We used to use all sorts of exotic features in StructureMap, decorators, named dependencies, auto-factories but over time I found these concepts are better made explicit in more specific utilities, rather than in the container.

Domain code cannot have interfaces named after the domain-driven language

I don't really understand this one. I don't see a functional difference between:

  • IValidator<CreateCustomerRequest> and ICreateCustomerRequestValidator
  • IHandler<CreateCustomerRequest> and ICreateCustomerRequestHandler

The ones on the left represent higher-level concepts and the ones on the right are a bunch of one-off, kinda similar but not really interfaces. I prefer the ones on the left, as I can now build far more interesting behaviors around these concepts than the ones on the right.

I built the things on the left to explicitly replace the ones on the right, but to say the names don't follow the domain is misleading - the domain language is still certainly there.

The domain code is polluted with Send and Handle methods

I...don't care? I use enough function-oriented frameworks where this has never mattered in practice. If you use any actor-oriented or message-oriented libraries you'll see the same thing, and it doesn't matter. It's because you've moved up a conceptual level and are building handlers for requests.

You can still certainly create domain services and the like, and I only use MediatR on the edges of my systems (with Vertical Slice Architecture). I don't ever see Send outside of a controller/page etc.

Domain classes are forced to implement interfaces defined in MediatR, ending up being coupled with a 3rd party library

I don't care. The whole "don't have your domain depend on 3rd party libraries" is silly in my opinion. It's taken the concept of POCOs well beyond what's valuable. These kinds of purity arguments don't result in code that's more maintainable, and the hoops people jump through to try to make this work make the end result more confusing. There's nothing magical about System.* and Microsoft.* over other namespaces and libraries, it's just a made-up line in the sand that adds little to no real business value.

Browsing code is harder (etc.)

Kinda? My requests are always declared adjacent to the handlers, and I use ReSharper's navigation to get straight to the handler. It's not much different than other similar patterns I use like this, where you trade off a more powerful pattern with some potential indirection. It has to be weighed.

Compiler gets confused and marks classes as unused

This isn't different than other implementations of this kind of generic pattern. Any time you start moving to these IFoo<T> patterns, where the implementations are automatically sucked up via assembly scanning, you'll see this. MediatR is one culprit, in my codebases I'll have several other instances of this. You learn to not care.

Performance

I've never looked at MediatR performance, because it's never come up as an issue. My handlers always hit a database or something, so anything MediatR does is a drop in the bucket. Probably could be improved? But in profiling real-world applications, there are SO many other places that affect performance.

MediatR can be easily replaced with trivial OOP techniques

Wellllll not really. The thing you can't do with trivial OOP techniques are cross-cutting concerns. It's not unlike any of the other examples I showed in my service locator post - once you start to move to generic interfaces AND want to provide behaviors around calling those interface, you're better served building your own class to encapsulate all the logic.

You could say, "just use a DI container's decorator feature!" Except the Microsoft DI container does not support decorators. So that's out. Even so, most container authors discourage heavy use of decorators because it puts so much burden on the container for building up this dependency graph at request-time, and it can often get this wrong. It increases confusion as well - what's the lifecycle of say Func<IDependency>? So I steer well clear of almost any feature that's not in the vanilla container UNLESS there is an explicit business need to.

So yes, you can use those techniques described - been there, done that, have the scars to show it. MediatR just solves those problems more explicitly and elegantly.