Alright, let’s talk about MediatR. You’ve probably heard the news – the popular .NET library, often the go-to for CQRS-style patterns, is shifting towards a commercial license for commercial use. Now, fair play to the maintainer for sustainability, but it definitely got me thinking. Do we really need a library like MediatR for decoupling our commands and queries? Often, the answer is simpler than you think.
This isn’t just about licensing costs; it’s a good chance to look at the complexity we sometimes invite into our codebases.
That Time MediatR Became the Maze Runner
I remember one project vividly. It started clean, using MediatR to nicely separate commands/queries from handlers. Standard stuff. But over time, it grew… wild. We had MediatR requests triggering other MediatR requests, sometimes nested. Notifications were flying everywhere, kicking off background jobs or updating read models in ways that weren’t immediately obvious.
Debugging became a nightmare. Trying to trace a single user action felt like navigating a maze. Where did the request actually go? Which notification handlers fired? Were they essential, or just side effects added later? We spent so much time just trying to understand the flow, time that could have been spent building features. It wasn’t MediatR’s fault per se, but the perceived “magic” it offered made it too easy to create tangled dependencies and obscure the core logic. It highlighted a big downside: when things get complex, that layer of abstraction can hinder understanding rather than help.
Quick Refresher: CQRS != MediatR
Just to be clear: CQRS (Command Query Responsibility Segregation) is a pattern. It’s about separating your write operations (Commands) from your read operations (Queries). MediatR is just one tool you can use to implement the dispatching part of that pattern. You absolutely don’t need it.
The core idea is simple: send a message (command/query) and have the right piece of code handle it. We can totally do that ourselves.
Rolling Your Own: A Simple Dispatcher Pattern
Let’s sketch out a lean, mean CQRS dispatcher using basic C# and the built-in dependency injection container.
1. Define Your Messages (Plain Old C# Objects)
Commands and Queries are just data carriers. Records are great for this.
|
|
2. Define Your Handlers (Interfaces + Classes)
Simple interfaces make it clear what handles what.
|
|
And the implementations:
|
|
3. The Dispatcher: Connecting the Dots
This is the heart of it. A simple service that uses the DI container to find and run the right handler.
|
|
4. Wire it Up (Dependency Injection)
Register your handlers and the dispatcher. Using a library like Scrutor
makes assembly scanning easy, but you can register them manually too.
|
|
5. Use It!
Inject IDispatcher
wherever you need it (like your API controllers).
|
|
Why Bother? The Payoff
- No Extra Dependency: One less library to worry about, manage, or pay for.
- Crystal Clear Flow: You know exactly how a command/query gets to its handler. Debugging is just stepping through your own code.
F12
(Go To Definition) onSendAsync
orQueryAsync
takes you straight to your dispatcher, not library internals. - Total Control: Want logging, validation, or transactions around your handlers? Implement it yourself using decorators or simple middleware logic in your dispatcher. No need to learn a library-specific pipeline model.
- Simplicity: It keeps the focus on the CQRS pattern itself, not on the tooling.
What About MediatR’s Bells and Whistles?
Now, let’s be clear: MediatR offers more than just simple dispatching, and its other features are often where its perceived value lies, especially for complex applications. It’s worth acknowledging these, particularly the point often raised about handling cross-cutting concerns:
-
Pipelines (Behaviors): This is arguably MediatR’s killer feature for many. It provides a clean, built-in mechanism (
IPipelineBehavior<TRequest, TResponse>
) to implement cross-cutting concerns – think logging, validation, transaction management, security checks, caching – that need to wrap around your command and query handlers. It’s a structured way to add logic before or after the actual handler runs.- The DIY Alternative: As mentioned, you can replicate this using the Decorator pattern combined with your Dependency Injection container. You’d create specific decorator classes (e.g.,
ValidationCommandHandlerDecorator<TCommand>
,TransactionCommandHandlerDecorator<TCommand>
) and configure your DI setup to apply them in the desired order, effectively building your own pipeline. - The Trade-Off: While the decorator approach gives you maximum transparency and control (you own the code, no library ‘magic’), let’s be honest: setting up and managing a robust, ordered pipeline with decorators manually requires significantly more upfront configuration and boilerplate code compared to leveraging MediatR’s pipeline feature. The convenience MediatR offers here is undeniable and a major reason teams adopt it. It’s a crucial point to weigh against the desire for fewer dependencies and ultimate explicitness.
- The DIY Alternative: As mentioned, you can replicate this using the Decorator pattern combined with your Dependency Injection container. You’d create specific decorator classes (e.g.,
-
Notifications: MediatR provides a publish/subscribe mechanism for “fire-and-forget” events where multiple independent handlers might need to react (e.g.,
OrderCreated
event triggering email sending, inventory update, etc.).- The DIY Alternative: For this, you can often implement a simpler observer pattern yourself. Create a dedicated
IEventDispatcher
(or similar) that resolvesIEnumerable<IEventHandler<TEvent>>
from your DI container and invokes all registered handlers. For more complex scenarios needing persistence, outbox patterns, or distributed messaging, integrating dedicated libraries like MassTransit or Rebus is often a better, more focused approach anyway, rather than relying solely on MediatR’s in-process notifications. Separating eventing from command/query dispatch can often lead to clearer boundaries.
- The DIY Alternative: For this, you can often implement a simpler observer pattern yourself. Create a dedicated
The key takeaway here isn’t necessarily that these features aren’t valuable, but whether the convenience they offer outweighs the potential complexity, dependency management, and now licensing considerations introduced by the library, especially if your primary need is just the core command/query dispatch. If you heavily rely on and benefit from the pipeline behaviors, MediatR might still be the right tool for you, but it’s worth understanding the alternatives and the trade-offs involved.
Wrapping Up
Look, MediatR has been useful for many, but this licensing change is a great nudge to question our dependencies. For the core job of dispatching commands and queries in a CQRS setup, a simple, home-rolled approach using standard .NET features is often more than enough.
It gives you back control, simplifies your stack, and avoids potential future costs or licensing headaches. Before automatically reaching for MediatR (or any library), ask yourself: could a few interfaces and a simple dispatcher class do the job? You might be surprised how often the answer is yes.
Disclaimer: The views expressed in this post are my own, based on my experiences and observations. Technology choices are often context-dependent, and I welcome different perspectives or challenges to my points in the comments below!
Further Reading
- CQRS Pattern:
- Microsoft Learn - CQRS Pattern: https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs
- TechTarget - What is CQRS: https://www.techtarget.com/searchapparchitecture/definition/CQRS-Command-Query-Responsibility-Segregation
- Dependency Injection in .NET:
- Microsoft Learn - Dependency Injection in .NET: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection
- Microsoft Learn - Dependency Injection Guidelines: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines
- Regarding MediatR Licensing (for context):
- Jimmy Bogard’s blog post discussing the MediatR dual license model (search for relevant post): https://jimmybogard.com/