Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
In my Fat Controller CQRS Diet series, I’ve shown the mediator pattern and the MediatR library. After a recent discussion with Reid Evans, he made me realize I haven’t really described the trade-offs. This post is going to focus on trade-offs and an alternative to using the mediator pattern. If you’re new to this series, here are earlier posts to get you up to speed:Coupling
With the mediator pattern, communication between objects is encapsulated with a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby lowering the coupling.For me the purpose of using the mediator pattern is to limit the coupling between integration boundaries. Those boundaries in my applications are typically the Web/UI Framework and my core application. This means I’ll have my controllers take a dependency on the mediator and not my core application.
Runtime Resolution
The downside to this that there is no compile time type checking. When the mediator is invoked at runtime, it could fail to send the request if there is no handler defined. In the code example above, if there is noIAsyncRequestHandler<Home, List<Album>>
then an exception will be thrown on _mediator.SendAsync(new Home());
We use a similar concept in our application. The way we address the potential run-time failures is through a combination of convention-over-configuration naming standards, attributes, and unit tests. Our naming convention is that all commands end in Command and the thing that handles the command ends in CommandHandler. We also want to ensure that each command handler identifies the command it handles. It’s then easy enough in a unit test to grab all the commands and make sure a handler exists for each one, and to grab each command handler and ensure its corresponding command exists.
Thanks for sharing your approach!
We run all our tests through the mediator so we can verify the proper registrations. I’ve been thinking about creating an analyzer with Roslyn. Good idea?
IMO, if it works and prevents run-time failures it’s a good idea. We just use plain ol’ reflection in our tests because (a) it works, (b) everyone understands it, and (c) Roslyn wasn’t around when the tests were written. We do have a couple of other tests that look at the code a little more deeply to ensure a certain pattern is followed (although I keep suggesting we should look at PostSharp to solve that particular problem, but I digress). For that we use MonoCecil, again because Roslyn wasn’t available when the test was written. Those tests I’d like to rewrite using Roslyn because I think they’d be easier to understand, but it’s hard to justify the time when the tests are working.
I find the suggestion to inject and call the handler directly very strange.
You are basically suggesting to go away from the mediator pattern?
It want limit or reduce the number of dependencies either, since you are just switching out IMediator to IHomeHandler.
The trade-off with the loose coupling with the mediator pattern is no compile time type checking. Alternatives to this would be to create tests that verify your code base contains matching command + handlers. However if you want compile time type checking, then yes, an alternative is to depend on a concrete handler type or interface.
The missing compile time check is a trade-off, yes. That’s the thing about loose coupling: Everything becomes more … loose … 🙂
Since the series from post 2 has been how to use mediator pattern to solve a problem, not using it doesn’t sound like a way of ‘getting around’ one of the challenges the pattern brings. IMHO it should be more clear from the text that this is what you suggest, so that readers don’t inject handlers and call them and think they still use mediator pattern and loose coupling…
Anyway; Our solution – or more like a band aid really – is manual inspection. We have made a way of inspecting what handlers are registered in the solution, and also the connection between them (the pipeline – what notification handlers exist for the completion event for a request, and what notification handlers might result in new requests being sent).
On top of that, we’ll add more tests on the IoC-container then what we have today.
I’m kind of curious of how and when other people use the Request / Query / Command (depending on if you’re using mediatR or a hand rolled ICommandHandler / IRequestHandler) pattern in their day-to-day.
I’ve been using this pattern for years, and it makes everything so clean and neat. With post and pre-processing, wrappers for cross cutting concerns, it really is a powerful system with one caveat: class explosion.
Now that we’ve moved away from IUserService / IUserRepository type interfaces (where one class violates the S in SOLID), a “Request” or a “Command” now requires upwards of 2-5 files (Request, RequestHandler, RequestValidator, and any other pre/post processors).
If you use a request / command to do just about anything in the system, this can generate THOUSANDS of files depending on how large your project is. One way we have combated context switching & file explosion in the IDE is by placing the Request, the RequestHandler, and the Request validator in one file (I know I know, but digging through 3 projects / folders / files hurts my brain for one singular piece of functionality).
I’d personally like some feedback on:
1. Do you generate a Command / Request for every INSERT / UPDATE / DELETE operation? Or do you limit it to specifically “business-y” logic? We have been simply because I can wrap my permissions / security CCS to block unwanted users from performing certain operations with 1 PermissionHandlerDecorator and the usage of a PermissionAttribute.
2. If not, how do you know when an operation should be wrapped in one?
3. How do you combat file explosion and / or context/project switching if you use separate files/projects for each request / command?
Any other tips would be great!
Thanks
Thanks for the comment.
I create requests for around actions the user wants to perform. Not really around CRUD database operations. Related to class explosion, I do what you have been, I generally try and put a command, handler, validation, etc all in the same file.
I’ve written about putting everything in the same file here: https://codeopinion.com/fat-controller-cqrs-diet-vertical-slices/