The Code Is What You Nurture, but the Dependency Tree Is the Application
You have to be able to see the dependency tree.
I think I have a preference for a particular flavor of configuration when it comes to dependency injection containers: doing it all in a method or a module that you can execute in a test. I suspect I’m not too opinionated about whether you do something like Guice or Dagger’s modules with provider methods on the module, separate provider classes, or whatever. What I think I don’t like is when there are annotations on the implementations classes, like @Autowired or @Service in Spring.
Spring’s annotation style encourages the class to know about how it’s going to be used in your program, which is a conflation of unrelated concerns. You might want to use it in different ways in different places. A class is merely a way to create an object that responds to methods. It knows what roles it depends on, but part of the point is that it doesn’t know how it fits into the larger picture of the application.
Dependency injection frameworks are more flexible than building your dependency tree manually, but the service they provide is not hiding or abstracting away that concern. The service they provide is allowing it to be separated from the individual pieces itself. It’s the ikea diagram that shows when each type of screw will be needed, which is separate from the specification for casting the screws themselves and drilling the holes they’ll eventually fit into.
When we look at OO programs, part of preserving flexibility is “decoupling” things by making them know as little about each other as possible– that’s the basis of practices like dependency injection and principles like connasence, or “depend on interfaces, not on classes”. This is great, but the runtime dependency tree for your application is still incredibly interesting, and needs to be something you can reason about simply and even potentially test.
Missing DI configuration is an extremely common reason for an application to get through a CI pipeline and yet fail to start up– despite having a well-factored and well-tested codebase. You should be able to write a test that makes sure that resolver gives you instances for the types you need. You might choose not to, but you need to be able to. That’s one of the great things about Dagger’s compile-time dependency resolution: omissions become compiler errors, although Dagger (at least in V1) required the most configuraiton of any DI framework I’ve ever worked with .
But this is the other problem with Spring’s design using classpath scanning and building up the dependency tree at runtime. Your classpath is likely to be different in test from in production, especially if you may have fake implementations of your interfaces in your test files. Since classpath scanning makes the dependency tree implicit, it’s hard to see it and touch it and feel confident that it’s going to behave properly in all edge cases.
The most explicit and stress-free DI framework I’ve seen is Autofac for .NET. In particular, I love its
InstancePerRequest method from the ASP.NET MVC extentions, like in this example:
This is a much more explicit, granular, and flexibly way of specifying whether you need a global singleton, a singleton in the context of a single request-response cycle, or just a new instance every time the dependency is needed. And this code in ASP.NET MVC’s
Application_Start() method. I like having all the code that explains the special cases on assembling the dependency graph in one place. Trivial mappings don’t need to have overwrought hand-written configuration: if
type D needs
A and B,
A has a default constructor,
C which has a default constructor. That’s the 80% case, and it’s trivially resolvable and it’s better for everyone if nobody writes code about those. But singletons and factories and runtime configuration setting injection should all be laid out somewhere close to the entry point of the application, in a single place so that it can all be reasoned about together. Not spread out over the application an intermingled with the declarations of the implementations of the various interfaces.
 I think Dagger is an impressive accomplishment. But Guice seems more convenient unless you’re working on an app that needs to start really fast, like an Android app.