Mediatr: Resolve only used subset of handlers, issue with IoC

Created on 26 May 2020  路  17Comments  路  Source: jbogard/MediatR

First of all, thanks for a great library, makes life and code so much easier.

The issue I am struggling with, I am not sure if it is .NET Core (3.1) or MediatR issue, but suppose you have the following setup

Common (library)
Service A
Service B

Now if you have a handler set S that are common across the services it may make sense to put them in the common lib, but A may need a subset and B may need another subset, not necessary overlapping.

What happens though, when you add the common assembly in the addMediatR in A or B is that it finds all the handler types in S and tries to resolve them, which is not guranteed as some dependencies are only available in A and others only available in B.

How do you work around this without duplicating the handlers? I am guessing it is not easy to solve as you would essentially have to scan out from the executing assembly to find all the handler types actually being referenced.

Most helpful comment

And I'm trying to be gentle here because I made these exact same mistakes about 10-12 years ago - but if you have to make a library or container go through all these convoluted gymnastics to get your application architecture to work properly, it's not the fault of the library/container. It's the fault of the architecture. It isn't actually simplifying things - it only gives the illusion of simplicity because code exhibits some organizational structure.

Just use folders for organization. It's fine. And vastly, vastly simpler. Use projects for units of shareable, deployable code.

All 17 comments

Typically your calling application has access to all of those libraries, and configures your container to be able to resolve all of those things.

Are your Service A and Service B different applications, and those have the implementations to dependencies defined in Common? If that's the case, then I'd just move those implementations to Common, since they are, common.

I'm also experiencing the same need.

Guessing that it is a common scenario to have several executable applications reference the same common library in one solution, but not using all the handlers defined there.
However, in such case (described by @arynaq) InvalidOperationException: Unable to resolve service <..> is thrown by DI framework on startup.

It would be very useful to be able to filter out classes in assemblies by namespace or an easy way to register single handlers.
Any workaround suggestions would be appreciated.

A few options:

  • Don't put these handlers in a common library if they're not actually common
  • Register dummy services for the missing implementations
  • Filter these handlers out yourself (don't call AddMediatR) when you register the handlers

I'm also having this same issue.

@jbogard concerning the workarounds:

  • Option1: Even if handlers aren't really common, I chose to put them in the same assembly so I don't end up with lots of projects having only 1 or 2 handlers.
  • Option2: If the dependecy graph for services is big, this will pollute the projoect and add lots of dummy classes. Also, adding a service for application A will break app B and C and I need to remember to register dummy implementations for all other projects that share the common one.
  • Option3: I couldn't find a way to filter my types and still let MediatR auto scan and register my handlers. I'm currently adding my handlers manually which is cumbersome but it's the least ugly workaround. I don't want to copy the type scanning/registring code in MediatR as that is highly techical code and specific to MediatR so it's best to leave it inside this library.

I'm adding my voice to previous ones. I think it would help to have a way to filter types that MediatR can scan. Maybe have an overload that accepts IEnumerable<TypeInfo> instead of only accepting IEnumerable<Assembly>.

@disklosr not sure what you mean by Option1. Are you saying that your "services" are small enough that they would only have 1 or 2 handlers, so instead you put them in the common project?

If you need more complex filtering / scanning for DI registration it might be worthwhile to use a different DI rather than the dotnet core DI (the AddMediatR function is only for the built in DI). There are examples of how to do this with popular DI frameworks.

It sounds like all of this are workarounds to design smells, to me. You shouldn't put handlers in a common library if they're not actually common. I don't know why you'd end up with lots of projects with only 1-2 handlers - you already have lots of projects because you have lots of applications. Just put the handlers in the application projects.

@lilasquared I meant that I have many independent handlers (not necessarily small) that if I split them I'll end up with lots of projects having.

Switching IoC containers seems a reasonable thing to do as this is really about scanning and registering handlers.
But if AddMediatR exists for the sole purpose of facilitating registration of handlers for those who decide to use the bare-bone dotnet built-in IoC container, then it might as well facilitate filtering.

@jbogard my handlers belong to the Application layer, my aspnet core projects are in UI layer, I do not want to mix the two.
I chose to combine all handlers in the same project first even if they're not actually common, and will split them progressively as needed.

My architecture is based on this if you need to see an example https://github.com/jasontaylordev/CleanArchitecture
only difference is I have sevral UI projects and one common project sharing all the handlers.

I'm not following the problem you're having any more. If all your handlers are defined in the application layer project, which should only have the one dependency of the domain layer project, then service A will just have access to all handlers when they are registered. Why do you have a need to filter the handlers so that different ones are registered to service A than to service B?

If you're having these problems, it's a sign that your architecture is wrong, not the library.

And I'm trying to be gentle here because I made these exact same mistakes about 10-12 years ago - but if you have to make a library or container go through all these convoluted gymnastics to get your application architecture to work properly, it's not the fault of the library/container. It's the fault of the architecture. It isn't actually simplifying things - it only gives the illusion of simplicity because code exhibits some organizational structure.

Just use folders for organization. It's fine. And vastly, vastly simpler. Use projects for units of shareable, deployable code.

@lilasquared arynac's description explains it well imo.

Let's say I have service A that uses Handler A that uses Dep A
Plus another service B that uses Handler B that uses Dep B

In service A, I register Dep A but not Dep B since it's not needed.
When I call AddMediatR() it registers Handler A and Handler B.
When I start Service A, the IoC container complains at startup because Handler B was registered but it can't be resolved since its dependency hasn't been registered.

If you have a handler in a common layer, with a dependency, then the dependency must be a common dependency too. Otherwise you don't have a common handler, and your "common" layer is not logically a common layer. The point of common is that anything can use it. If that's not the case then something is wrong there.

This could be considered "a problem" of the .net core DI engine, not this library. Also is a design problem because something that is common, at the end is not. You should consider splitting the common into 2 or 3 or N "common" libraries, and that should solve the problem too

Hello from Twitter 馃憢

Here's a visual representation of what I believe is the architecture being described here.

image

If this is your architecture then I think the best thing you can do is move the handlers to the service assemblies. Organizing types by type-of-thing (service, interface, handler, controller, model, view) is a practice that doesn't scale well. You just discovered one reason why.

Alright it seems everyone here is suggesting that all this smells an architectural issue and that it's not really the fault of this library. Fair enough. I thank you all for trying to help and do respect all of your opnion and advice and will definnetly take them into consideration.

To me the problem I'm facing is ultimately a limitation of the DI library which is not being able to discover types at a finer level than an Assembly, and I do agree it is not the responsability of this library to make up for this.

I'd like to clarify some things:

  • I'm organizing my projects by responsability/concerns. I do not have a project just for the sake of grouping all handlers together, I rather have an Application Layer project of the Clean Architecture that have a clear responsability and contains application layer services which happen to be implemented as handlers, plus an anti-corruption layer.
  • My "common" project is not really common, not in the sense that it can be referenced by everyone. I logically have three distinct application layers corresponding to 3 different REST APIs having a shared domain model. To simplify the structure of my solution, I organised my 3 application layers in separate folders inside a same project, as opposed to putting them into different projects.

Most of you suggested to put handlers in my UI layer. Here are my current thoughts on this:

  • It will make my architecture less clean since my handlers, which represent my application services, are now inside UI layer.
  • Since I think this issue is mainly due to IoC container I use, changing my architecture as suggested seems like bending the rules to make up for my IoC container's limitations, which doesn't seem right to me.
  • My handlers while being in the same project for the sake of simplicity, they are separated into different namespaces. Discovering and registering handlers by namespace instead of Assembly doesn't seem like convoluted gymnastics to me.
  • I think

I'll try to rethink my architecture through and will consult other people on this. Would love to hear what @arynaq and @domasd think about this.

Again, thank you all for your contributions!

To be clear, I'm suggesting not just moving your handlers to your UI project, but to get rid of all of the layers altogether:

https://vimeo.com/131633177
https://www.youtube.com/watch?v=SUiWfhAhgQw

The issue is conflating LOGICAL boundaries with PHYSICAL ones. You're not violating some fundamental law by using folders instead of projects to organize. And as you've found, you have to bend and twist your DI container to do these indirection tricks (or multiply your projects).

And knowing several DI container authors personally, they *hate* this architecture because it tries to force the DI container to do far more than it ever was designed to be. You won't get much more help from those folks either 馃槅

Good luck!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tool-cmd picture tool-cmd  路  4Comments

TDK1964 picture TDK1964  路  5Comments

developermj picture developermj  路  6Comments

jakerabjohns15 picture jakerabjohns15  路  5Comments

grokky1 picture grokky1  路  4Comments