A recipe for understanding MediatR for Vertical Slice Architecture

July 30, 2024

We're going to use Jimmy Bogard's "vertical slice architecture" version of the Microsoft's Contoso University application to try gain some understanding of what MediatR might be able to do for us. The application references the MediatR Nuget packages, but just using the Nuget package doesn't really give us the same insight that stepping through the source code does. So were going replace NuGet packages with source and debug to see what we can learn.

Here's the steps to setup for debugging:

  1. The first thing to do is to clone the Contoso University application source code. There's a myriad of ways of doing this depending on what tools you are using, e.g. Visual Studio, Code, Jetbrain's Rider, etc. I'll use the most common denominator, git from a shell. Before we do that we'll need a few folders/directories to work with so we'll start by creating a root directory to hold everything. I'll be using the "source" directory located in my local machine's user directory. Use what you like.

  2. At a command prompt, using your favorite windows/Linux shell, create a folder name mediatR - e.g. mkdir mediatR.

  3. Next clone the Contoso project into a sub-directory within mediatR - e.g. git clone https://github.com/jbogard/ContosoUniversityDotNetCore-Pages.git

  4. Again using your IDE/editor of choice take a look at the project references to MediatR and MediatR.Extensions.Microsoft.DependencyInjection. Note the version "11.0.0". Use or find and navigate to the URL's for the repos for both of these projects on github. Then remove the project references to both of these, again using your tooling of choice.

  5. Next using the URL's from the previous step, clone those projects into sub-directories of their own inside of the mediatR directory you initially created. It actually doesn't matter where you put things, as long as you know where you to find them.

  6. The git commands using the cli will be "git clone https://github.com/jbogard/MediatR.git" and "git clone https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection.git".

  7. Next add the 3 references to these back into your Contoso project as project references, as opposed to NuGet packages. I recommend putting all of these in sibling directories as in the previous steps so that you can create a solution includes three of the projects. Tooling including the .NET CLI will do here.

  8. Since the Contoso project specifically makes use of the version "11.0.0" of both of the other packages/project, you'll need to use git to checkout that exact version of the code. E.g. "git checkout v11.0.0" on each dependency project.

At this point you should do full clean/builds and there should be no errors.

Start stepping/debugging:

Theres different ways of doing this but I arbitrarily chose to debug the Students/Details.cshtml.cs file. Find and open that file and put a breakpoint on "OnGetAsync". The implementation is an expression-bodied member calling "Send" on an IMediator object that is populated via the IOC and the class constructor.

Side track - I'll point out one thing that I don't like in this source code already. When I use third party packages in an application I prefer to define my own interface and then use the adapter pattern to delegate to the implementation in that package. By doing so I've creating a firewall of protection between my application and ny third party packages. Thus if I need to change to a different implementation of my own interface I can do so without dependency changes rippling through my code and potentially requiring code changes in multiple places and regression issues. This may or may not be overkill, but I can't help point out that this is the same principle behind mediatR's supposed philosophy of decoupling implementation from client/callers. If you're going to adhere to a philosophy then drink all of the Kool Aid everywhere all of the time! Here this Contoso code violates that principle by directly depending on a third party package. To me that's less than ideal, regardless of what label/architecture you are using as you are opening yourself to churn controlled by others rather than your own self or organization. Put control into your own hands, not in the hands of others.

OK, I'm using Visual Studio so it's F5 and off to the races. When the browser comes up drill down to any student and click the details link. At this point you should hit a breakpoint on "OnGetAnyc". Now that we've got the project/source rather than just a binary, we should be able step into that method. If all goes well you'll find your instruction pointer in an overload of the Send method of the concrete Mediator class. If you don't, then use your tooling to find that, set a breakpoint here, and repeat until you do. You may find that you need to update build configurations to include a debug build, build symbols, or turn off "just my code" debugging in order to get stepping into the MediatR code to work. I found that Visual Studio gets confused around using the source versus the (generated? downloaded?) symbols from the binaries of the Nuget packages.

Once you sort that out, you'll find yourself (instruction pointer) in the following code:

```

public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
{
    if (request == null)
    {
        throw new ArgumentNullException(nameof(request));
    }

    var requestType = request.GetType();

    var handler = (RequestHandlerWrapper<TResponse>)_requestHandlers.GetOrAdd(requestType,
        static t => (RequestHandlerBase)(Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(t, typeof(TResponse)))
                                         ?? throw new InvalidOperationException($"Could not create wrapper type for {t}")));

    return handler.Handle(request, _serviceFactory, cancellationToken);
}

```

Analysis of what we observe

This is a little nasty to look at, but if you break it down it's not rocket science. Take the time to reformat/refactor this using again the tool of your choice if necessary. I did. There's really only 2 things going on here. First this method looks for an entry in a container who's key is a Type. If the item is there it's returned. If it's not there, then its creates an object using the 2nd parameter - a factory function, adding it to the container, then invoking "Handle" on that instance and returning the result. In this case the object being returned is a generic type that's being instantiated using reflection. It's complex looking because of the generic type and the casting and null checking , but you can see there's a call to Activator.CreateInstance. That's a thing that creates objects using reflection. It is in fact finding or initially creating an instance of the QueryHandler defined there in the Students.Details.cshtml.cs file. There may be other features to mediatR, but I believe this to be the gist. This is the supposed mediator that allows the PageModel in this case (could have been a controller for MVC or an API app, etc.) to find/create a handler(s) and invoke it.

OK, so another side-track. I don't understand the need to decouple the PageModel (or controller or whatever) from the logic that needs to execute. To me, this is just another layer, like the too many layers Bogard accuses/points the finger at "clean architecture". To me, this PageModel (or controller if it had been one) is defined to support a specific use case. I want it to be coupled to a domain object that is going to be responsible for whatever behavior is needed for the use case. I don't want it knowing or implementing the rules, or the data access, it should only delegate to a domain object like the delegation that is going on here with the QueryHandler, but I don't need my PageModel or Controller to be decoupled from it. It's not like I'm going to re-use the PageModel or Controller for something else. I also don't like the fact that the QueryHandler that is invoked here is directly coupled to a DBContext. If I were doing something like this I'd again define an interface that represents the data operations that were needed and hide my DBContext as an implementation detail there. The scope of that data access interface should be narrow, e.g. only and members to support the specific feature or use case, i.e. the vertical slice. This creates a firewall in my code to protect against changes to persistence - e.g. infrastructure. This is an example of a layer still being useful. I'd be more open to the type of leaky abstraction I see here if there were other expression tree to database providers that could be plugged in (e.g. Open-Closed), but I'm not aware of many other than EF.

Conclusions

So what do we have here? Bogard has a post where he defends/comments on criticisms of MedatR here . Based on what I've seen here I have to agree with some of the the criticisms:

It's definitely implementing a service locater. Dont' take my word for it. Look up or ask Copilot what are the defining characteristics of IOC versus service locater. Mind you, sometimes one needs to use service locater when the type/method to resolve to can only be determined at runtime, or you are implementing using a dynamic language (e.g. Python), but that's not the case here as all types are known at compile time.

well, sort of. Seeing the dependencies is not as simple clear until you go through this exercise.

well perhaps not interfaces, but methods for sure

It most certainly is.

I've already pointed this out and clearly Bogard's response to this suggests he doesn't understand the value of using the adapter pattern to firewall off 3rd party dependencies, but perhaps not surprising as in my experience most developers don't. I do like that the pattern you must follow guides a developer into granular class with a single responsibility, so if your devs don't have that skill then it could be a bonus. Always trade-offs.

Just using normal dependency injection and delegating to a domain object would be much more straight forward, simpler.

Summary

I like this idea of "vertical slices" or modularity based on features, use cases etc. I like the name that Bogard has given it. I came up with the same idea on my own when observing challenges with large application code base based on layers only and the problems that came with that. Other than the name, this idea is really nothing new if you understand both Martin's SOLID and package principles, perhaps most importantly the "Interface Segregation" and "Common Closure Principle" which have been around for a very long time.

I'm sure one can successfully build maintainable applications using this approach, but I don't see "vertical slices" as something mutually exclusive to layers or better than "clean architecture" as his initial post seems to suggest. Unfortunately the link to https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html is broken so I cannot see what was stated there which may support Bogard's viewpoint. Other than that, vertical slice architecture just a new name for a very old idea; modularity. Clearly one can implement "vertical slices" with or without MediatR. I'd probably start without. I don't care to reinvent the wheel, but the fewer dependencies in my code the better, and the more control I have over that application.