I was only recently introduced to MediatR through the magic of Reddit.
Jimmy Bogard also does the excellent AutoMapper tool so it was work looking into. He’s written about his use of it for a long while on his blog
Flattening the layers
His posts on implementation patterns and dealing with duplication are the real gems though.
My particular problem is that in my three layers in my REST API, I find I’m constantly injecting new classes to try to avoid duplication of logic. This MSDN post actually articulates the problem I’ve got with repositories and whatnot.
Given my layers:
- Web (Controllers in ASP.NET Core terms)
- Business stuff
- Data Access
I want to keep some separation to concentrate on just testable logic my business layer. Web should just transform a request and call the relevent business object. The business logic needs data sometimes.
He says “I want MediatR to serve as the outermost window into the actual domain-specific behavior in my application” which is great. The end result is a Controller class that barely does anything except call
However, I don’t want a handler that just duplicates my problem but just hides it behind MediatR so only my controller is prettier. How do I organize things to be simplier while still having some layering with reusability and little duplication?
Handlers calling Handlers
Really what happens is that my “business” layer handlers end up calling other business handlers or data handlers.
My unit tests look like a series of these Moq statements:
mediator.Setup(x => x.Send(It.IsAny(), CancellationToken.None))
.Callback<IRequest, CancellationToken>((r,y) =>
var x = (Locations.NewLocation) r;
I’m validating that I’m passing a message with MediatR and validating the message’s contents.
Is this bad?
It seems like it is.
MediatR basically just divides everything with loosely coupled message passing. It could all end up as a huge message soup with tons of layers of indirection.
Jimmy has a good example of how he uses MediatR, AutoMapper and other things with ASP.NET Core on github: https://github.com/jbogard/ContosoUniversityCore
However, the logic is just a basic CRUD app. Nothing needs to share anything.
Is there anything that can stop that?
Just discipline I guess.
The good: a cache in the pipeline!
(I’m waving my hands with the implementation details of caching as that’s code for another post.)
I put a Redis cache on my reference data from the database. I had a pattern around my data access but it was a lot of copy/paste. Now, I have a marker interface for my requests and it just automatically caches because the MediatR pipeline just resolves it.
The cache pipeline handler with a marker interface is declared like this:
public class DistributedCachingHandler<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : class, IUseDistributedCache
where TResponse : class
Now, any request that goes through MediatR that implements the
IUseDistributedCache will go through this pipeline handler.
Actually, the generics and type resolution is not done by MediatR but by your IoC container of choice. I was sticking to the default container in ASP.NET Core. However, the resolution isn’t as sophisticated as StructureMap, AutoFac, etc. and ends up erroring when trying to create pipelined types generically constrained by the marker the interface. So now, I just plugged in AutoFac and still use
IServiceCollection as I normally would.
The good part 2: FluentValidation and MediatR
(I’m waving my hands with the implementation details of this as that’s code for another post.)
FluentValidation is a good library for creating validation classes for various POCOs then the validation can just be plugged into anywhere. I want this to be plugged into two places: when ASP.NET Core is accepting a model from a REST call (using an ActionFilter) and also in my MediatR pipeline!
I made a MediatR pipeline handler that takes any request/response pair and sends it through FluentValidation. If any Validators are registered, then they are ran.
For the Action Filter, FluentValidation is chained onto your
AddMvc method and marks the
ModelState as invalid. You can handle this in many ways but I made another Action Filter to automatically return when the Model is invalid.