simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.21k stars 155 forks source link

Conforming Microsoft.Extensions.DependencyInjection Adapter #875

Closed mikernet closed 3 years ago

mikernet commented 3 years ago

I posted this in Gitter but seems like it is dead over there so I figured I would move it here for discussion:

I've extensively read through all the old discussions you (@dotnetjunkie) had with the MS extensions people about the difficulty in making a conforming MSDI adapter for SI. I believe I have a firm grasp of your grievances and the issues involved as well as the non-conforming approach you ended up going with.

I believe there is a way to accomplish a conforming adapter in an ideal manner with few downsides and would like your thoughts. Most of this would boil down to tracking which registrations are done with the adapter and which ones are done directly on SI and modifying the behavior accordingly. Verify() would only do full verification on registrations that were done directly on SI with this approach, which while I understand you don't like, I think is a good compromise. I think SI users would understand that registrations through MS DI interfaces are only for libraries that depend on MS DI and your app should use SI directly and that full verification for registrations done through the MS DI interfaces was not supported.

I think the issue of explicit scopes in MS DI could also be solved by simply adding that capability, but only making it available through the MS DI interfaces. It's possible that I'm not fully considering the problems this would cause though, so reason for why this is a bad idea would are certainly welcome.

I'm curious what showstoppers you foresee with this approach and whether you would consider contributions to make this happen. If not, why not?

Being able to use SI in ASP.NET Core without the awkwardness of cross-wiring would be a huge win in my opinion, so I'm interested in helping do whatever is necessary to make this happen if it's possible.

P.S. I can't even fathom using another container after using SI...I can't stress enough how great a job you've done with SI <3

dotnetjunkie commented 3 years ago

Hi Mike,

Great to see you read through some (I bet not all, because there are quite some) of the discussions I had with Microsoft. You are likely also familiar with this blog post on the official Simple Injector blog. If not, I urge you to read it, because it does a decent summary of the multitude of problems anyone creating an adapter is facing, and especially in the context of Simple Injector.

That blog post describes that verification is just one of the many problems that need to be tackled. The main issues mentioned there are:

That blog post, however, only touches the surface and doesn't really go into details why those issues are hard to solve. I think I best summarized the problem in this discussion with Fowler (if you haven't already, the whole thread is a worthy read btw):

although it would be technically possible to create an adapter for Simple Injector; by doing so the adapter would change the behavior of Simple Injector in such radical way that it becomes a totally different library. It would become a library that loses some of its unique and compelling features. Mark Seemann terms this it stifles innovation.

That specific comment of mine especially goes into depth about the unbridgeable differences between how collections are registered between the two libraries. And the more you investigate this, the more complex just this single issue becomes to solve. It would force complexity on all sides:

To dive a little bit deeper, here's just one of the issues that need to be solved with regards to the collection incompatibilities: Just consider what it means when a user appends a registration to a collection. If that collection was made through the adapter (by the framework or a third-party library), but the user appends it using the Simple Injector API, should it follow framework semantics or Simple Injector semantics? For instance, when a third-party component asks for just one instance, should it get injected with this registration made through the Simple Injector API, or should get the last registration made by the framework/third party? Both paths will lead to confusion in some way.

Another part of this incompatibility analysis can be found later on in that thread. The analysis is quite abstract but is one you need to solve when creating an adapter.

Because of all the above, I see no future in building an adapter and will not allow any attempts to do so under the official Simple Injector flag (i.e. promoted as official Simple Injector package, maintained under the Simple Injector GitHub account).

But feel free to create a new GitHub repository where you try building such adapter (there might already exist a few of such attempts), if you think this is feasible. I'm willing to give you my feedback and raise incompatibilities as I see them and engage in discussions and answer questions during development, but please don’t expect me to provide support to to users of that adapter. I simply won’t have the time for that.

Be aware, however, that I'm certainly not willing to make changes to the Simple Injector core library that conflict with its design principles, and I'm very reluctant to add extensibility points inside the core library that have a (big) negative impact on the complexity of that library.

Also note, that -with the help of many others- I've been building and maintaining Simple Injector for over a decade now. Users would expect the same support for such adapter. If you're building such library, you'll be in it for the long run, and are expected to try to deal with questions and issues as they come along. No pressure ;-)

mikernet commented 3 years ago

I bet not all, because there are quite some

You would be surprised :)

You are likely also familiar with this blog post

Indeed I am. I know how different SI is from MSDI, and while the abstractions provided may not be ideal from the perspective of SI's architecture, it's the one we got to work with. Believe me, I also empathize with your distaste for some of the design decisions made, but I've come to terms with the fact that some kind of standardized DI abstraction for ASP.NET Core is better than none and if they would have done anything differently they would have just pissed someone else off instead, haha...can't please everyone I guess.

although it would be technically possible to create an adapter for Simple Injector; by doing so the adapter would change the behavior of Simple Injector in such radical way that it becomes a totally different library

I think we are looking at this in different ways. Simple Injector will be exactly the same when used like it is used today through its native interfaces. It's would only expose radically different behavior when it is used through the abstractions, which are provided solely for ASP.NET system services.

not willing to make changes to the Simple Injector core library that conflict with its design principles

Conforming adapters inherently conflict with simple injector's design principles, but that doesn't mean that simple injector, when used directly via its "native" API has to conflict with those design principles...it just means that when you use it via the adapter those design principles may no longer apply to those registrations since those obviously follow MSDI design principles.

While it may be possible to do without SI cooperation, I feel like it would be at least an order of magnitude more difficult without the cooperation of some core changes to SI to facilitate the adapter. Let me think about what the possible approaches here could be and get back to you about what a minimal set of changes to SI would look like.

I suppose what I was initially hoping for was a willingness to accept some relatively deep changes to SI to facilitate the "dual" modes of operation. Nothing, not a single thing, would have to change in terms of how SI works through the native interfaces. I fear that without a willingness to go in this direction, SI may fall into obscurity. A very large portion of .NET developers develop in ASP.NET as well as the other technologies, and while I absolutely love SI, the current approach just doesn't seem particularly optimal - the cross-wiring bits for ASP.NET are just too awkward and that means that devs will learn and move to other DI systems and probably not bother to go back once they are familiar with something else that "just works" everywhere. I think the lack of SI being listed as an option in the ASP.NET supported DI containers is really unfortunate as well.

omarjuul commented 3 years ago

Conforming adapters inherently conflict with simple injector's design principles, but that doesn't mean that simple injector, when used directly via its "native" API has to conflict with those design principles...it just means that when you use it via the adapter those design principles may no longer apply to those registrations since those obviously follow MSDI design principles.

Sounds to me like you want SI to basically become two separate containers, where registrations are made to one of the containers, depending on which method is used to make the registration. That sounds very confusing to the end user, in my opinion. Why not use 2 containers for that, and make the interaction explicit? That's exactly what cross-wiring does, right? You say cross-wiring is awkward, but what you're proposing still would need some cross-wiring as there would basically be two containers hidden in one. Except in that situation the cross-wiring would be implicit and invisible. You could call that an advantage, but as I said I think it would most likely lead to a lot of confusing behavior.

mikernet commented 3 years ago

The cross-wiring now is just very awkward and it doesn't quite mesh 100% between the two worlds. It works okay in one direction and in the other it's entirely manual and blah as all hell.

If documented properly (and who doesn't read DI container docs before using it?) I think it would make sense. I have a couple ideas for how this could work. Anyone using SI with MSDI right now will already have to wrap their mind around integrating 2 containers. It took me a while to do so anyway. I don't really see this as being more difficult to comprehend, quite to the contrary actually.

mikernet commented 3 years ago

The other issue with the dual container approach is that you need all these custom mechanisms to tie into certain areas of ASP.NET DI resolution (i.e. middleware services) instead of just being able to use the container normally.

I think these issues can be solved with a nicer approach that (at least for me) will make more sense. If you have no problem right now understanding the nuances of how the 2 containers work and how to make them interoperate then I see no reason why the approach I am envisioning won't be a strict improvement in terms of user-friendliness and ease of use to achieve a similar end in a less error-prone manner.

I need to play around with a POC for now to make sure this can work how I think it can. I'll report back soon :)