Dependency Injection - NOT
I first heard the term dependency injection (DI) a couple of years ago when some folks were talking about UberFX and how DI was the hot new thing and would save us all. And I was very skeptical, because why would I want someone injecting random code into my process? Code injection is an attack vector, not a design principle. Why would anyone build a system around that?
But the thing is, Dependency Injection isn't actually injection. And it's certainly not the injection of random code without the developer's consent. I wasn't in charge of figuring out the name for DI, but if you were to ask me, I'd say they got it almost completely backwards. It's not "injection", its "demand" and "consumption". Instead of finding/creating the things you need, you just tell the "system" what you need and it hands it to you.
There are lots of advantages to this method. First, you're programing to bare interfaces, not specific implementations, so separation of concerns is baked right in. That's a big reduction in cognitive load right there.
Second, whatever system is providing you with your dependencies can update them and the next time you start you get the update. That's great for bug fixes, vulnerability batching, feature updates, and infrastructure changes. As long as the interface doesn't change the calling system can make whatever changes it wants.
Third, automated is much easier. When the system under test is created, the test framework can just hand it
special implementations of the interfaces that have additional testability hooks. For instance, the
infra
CLI accesses both the network and the local file system. When testing the network and FS
interfaces passed in provide all kinds of hooks to let the tests control the results of calls, but to the
infra
CLI itself, absolutely nothing has changed.
UberFX (and JavaFX I'm told, but haven't used) goes one step further. It includes a bunch of standard interfaces (network, logging, metrics, etc), and you can add your own interfaces, each with its own dependencies. Then, when you try to start the program it analyzes those dependencies and builds an ordered graph of the order to call them and then passes the results on to the next layer as needed.
So despite its terrible name, DI is actually a useful system that enforces separation of concern and control inversion. The next question is of course, "If DI is so good, shouldn't we use it everywhere and stop linking against libraries?", but that's a question for another time.