3

Recently I started working on a rather large monolithic system. The solution has web, app and api parts and in general just a lot of stuff.

It relies heavily on dependency injection and I noticed that this makes it really hard to figure out what’s happening.

A search for a controller name returns multiple results and when I finally figure out which one I need, some service is injected or otherwise located, and I have to start searching again.

By simply newing up an instance of an application service in a controller, the flow of the application is much easier to follow. I think it also lowers chances for runtime errors. Note that I’m not talking about adding any logic to the controllers, that is delegated to an application service layer.

Lastly, there aren’t any unit tests for the controllers, so I wondered: what am I missing here? Is there a benefit to DI I’m not aware of?

7
  • 3
    The title ask Is DI always a best practice ? but the question is much closer to Is DI ever a good practice ?. The answer to Is X always a best practice ? is always No, whatever X may be. As to Is DI ever a good practice ?, let's wait and see what the experts think. Mar 12, 2019 at 19:53
  • 2
    Do you mean using a dependency injection framework, or just dependency injection in general? Mar 12, 2019 at 21:35
  • Yeah, IME dependency injection is all too often nothing more than code obfuscation. You know the old saying? The road to hell is paved with good intentions. You may have inherited this monolith from some junior developers who really wanted to create a "clean" architecture where everything is abstracted and configurable, and now it turned out to be an unmaintainable mess because they had forgotten about even more basic good practices such as "use before reuse" or "simplicity before generality". Mar 13, 2019 at 8:22
  • There is one major advantage of DI, namely that you - the programmer - are not responsible for carrying information across the application to each and every location that needs it. A common example is configuration variables where you either need to carry the information around to each and every module that needs it, centralize it exposing internals, or stuff them in global variables. By using DI for this, your code becomes cleaner. Mar 13, 2019 at 10:03
  • By simply newing up an instance of an application service simply doing that in every single Controller which will have to know all the app service dependencies to instantiate the service, and the dependencies of the dependencies and so on... Simply duplicating code all over the application. A blatant violation of DRY.
    – Laiv
    Mar 13, 2019 at 13:51

4 Answers 4

14

Is dependency injection always a best practice?

Yes, passing your dependencies into their consumers is always a best practice. It allows flexibility in managing those dependencies, ease of testing, ease of changing those dependencies, ease of refactoring the dependencies themselves... it's a key aspect to decoupling code of all sorts.

Inversion of Control containers are... less clear a benefit. They can obfuscate dependencies, add coupling to the container itself, and generally hide things behind black magic making them hard to customize.

And even though it is a best practice, it's not a rule. Sometimes just spinning up your dependency is simpler and the benefits aren't worth it in that situation. Sometimes the dependencies should be tightly coupled to provide friction against misuse. But those cases are rare. You should default to passing in dependencies.

7
  • 2
    Sometimes the dependencies should be tightly coupled to provide friction against misuse. But those cases are rare. You should default to passing in dependencies. at the early stages of the design, there's nothing wrong on tightly coupling the dependency and leave for later the decision to get it injected or not. It would just take a refactor but by the time you do it, you already have a better perspective of the design and the "why"s you should allow injection. Injecting by the pleasure of flexibility (a feature you might well not need yet or not yet there), it's overengineering.
    – Laiv
    Mar 13, 2019 at 8:38
  • 1
    @Laiv - ehhh... in my experience "just take a refactor" doesn't always happen. Given the ease of setting up basic injection, I'd err on the side of just doing it right the first time.
    – Telastyn
    Mar 13, 2019 at 16:07
  • Ok. My bad, I have oversimplified my arguments. What I want to say is that parametrization and abstractions are not always evident at the beginning of the design. However, It takes few iterations to see whether you need them or not, and a few more for moving a dependency to a method and finally replace It with an abstraction. But when you do It, you already have a reason for it. Without a good reason, it is just YAGNI. That said. Does DI is good? Yes. Very good. Always? No. It's good when It solves a design problem. DI for the sake of flexibility is overengineering. IMO.
    – Laiv
    Mar 13, 2019 at 16:50
  • That said, I upvoted your answer. I agreed. Just wanted to qualify that "Yes".
    – Laiv
    Mar 13, 2019 at 16:54
  • I understand your argument, but disagree that it's overengineering. Well that's not true, I agree with it being overengineering when you're starting a project and don't quite know what you need - but I'd rather overengineer than underengineer stuff when the cost is as small as basic DI usually is. Because for 90% of code, there's going to be a design problem that DI will help with.
    – Telastyn
    Mar 13, 2019 at 17:23
8

No pattern is always a best practice. Patterns are tools like a hammer or a wheelbarrow: The are useful for particular tasks, and you have to use them correct to get any benefit. If someone says "it is best practice to always use a chainsaw, regardless of the task at hand" don't listen. They are probably trying to sell you a chainsaw.

While I cant judge the code you describe, it sounds like DI is used in way which makes the code unnecessary complex, without providing the supposed benefit. This could be because the original developers heard "DI is always a good practice" without really understanding and so implemented it in a way where it doesn't provide any benefit.

1

You're using .NET? Yes, dependency injection is always a best practice. I say that having worked in .NET over a period of 10 years; some projects had heavy DI and some had none and some had service locators. I believe all computer languages with reflection support benefit from dependency injection, and it's more important for large applications than small scripts.

When I think of benefits of DI, I think of clean code, easy unit testing, and orchestrated (or per-market) deployments. There are some great books that cover this in depth. One is "Domain Driven Design" by Eric Evans. In there he talks about organizing your code into services, entities, and values. Another great book on .NET in particular is Marc Seemann's "Dependency Injection in .NET". And here's an article I wrote on this topic a while back: https://msdn.microsoft.com/en-us/magazine/dn683797.aspx I also recommend that you read some of Rocky Lhotka's works on application layers -- DI is so nice for allowing per-layer software testing.

Some injector frameworks are better than others. The out-of-the-box asp.net injection can be painful. You can replace that one. There are other options. I recommend DryIoc, as it is one of the few injector frameworks that supports per-graph dependency injection (look that up and use it). And don't load dependencies asynchronously when debugging.

1
  • 2
    It's all good advice... for someone who already knows it. For someone who doesn't, your answer could benefit from some examples that demonstrate your assertions. Mar 13, 2019 at 5:27
1

It is OK to not inject dependencies when you can do it. For example, if you operate with some documents which are pure piece of data not referring to any external storage (for example, database), then you can create those with new etc.

Then, as I see it, there are basically 2 use cases for the dependency injections: mocking for testing and real production cases when you have several implementations of the same interface.

The testing injection are usually more ad-hoc and small scale, and this is sort of necessary evil. Still, if it is possible to implement your testable logic as pure functions which uses only data, it is preferable to use that one. When you navigate the code, you should be able to pick the production implementation when you are searching for definition.

The production injection has larger scale and should be well designed to isolate concerns of the dependency and the code where it is injected to. If you have to navigate back and forth between the dependency implementation and its usage to comprehend how it works it is a sign that the separation was done poorly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.