2

I'm working on this code base that, at a first glance, seemed to be doing dependency injection properly. However, doing some digging, I found that what's actually happening is that there's a Dictionary<IType, Type> being maintained in one of the classes, which allows devs to "register" the implementation type associated with an interface. Then, when a service is required, this class queries the dictionary for an existing instance, and if none, uses the Activator class to new one up (also inserting it into the dictionary for further reuse).

It's worth mentioning that this project is an SDK distributed via NuGet, and we cannot delegate DI resolve responsibility to end-users (would break the encapsulation principle, and besides, users can't be familiar with the intrinsic implementation details of each entity so as to be able to resolve the DI aspect on their end with whatever IoC container they're using).

All that said, my gut feeling tells me this isn't proper DI that I'm looking at here. I could be wrong, but it seems to me that this practice can lead to a number of different issues. Am I overthinking it?

I guess the real question would be, what are the best practices in terms of resolving DI specifically in a self-contained class library project distributed as an SDK?

stuartd
  • 70,509
  • 14
  • 132
  • 163
Lewis
  • 41
  • 4

1 Answers1

0

Composition Root

Best practice is that everyone accepts their dependencies via constructor. This applies to classes in class libraries, console apps, api projects, or anything else (that accepts it, older frameworks don't work nicely with this).

Then, at the very top, whoever is actually running the code is responsible for resolving all the dependencies. Either manually or with the help of some DI container.

This allows code to have different dependencies based on runtime environment (i.e. Prod vs local, vs unit tests).

In your specific example, a self-contained class library project distributed as a NuGet package; the classes should accept their dependencies in the constructor and not worry about how they will be resolved.

There is a problem though, sometimes a project grows complex with dozens of dependencies and you don't want your consumer to have to resolve all of those especially if sensible defaults exist. In that case the library author may want to provide extra functionality to help resolve all those dependencies. You'll commonly see this in .NET applications in the form of builder.Services.AddProductName();.

hatcyl
  • 2,190
  • 2
  • 21
  • 24
  • 1
    Thanks, @hatcyl. The code base does have something that resembles what you mentioned in your last line, `builder.Services.WithService(...)`, but... it does feel weird that every time one needs to instantiate a service, they'll need to first instantiate all of its dependencies in order to pass it into the desired service's constructor. I was just thinking there **must** be a better way. On a side note, not sure why this question was closed for being `opinion-based`. What is the use of this platform, if I can't ask the professional opinion of the community?! – Lewis Feb 22 '23 at 15:49
  • Questions like these are better received at Software Engineering Stack Exchange https://softwareengineering.stackexchange.com/ ... with DI you do have to resolve the dependencies once for each service. As I mentioned in the last paragraph, if you don't want the client to have to do that then you can provide helper methods. – hatcyl Feb 24 '23 at 03:37