DI Containers are Code Polluters
While dependency injection (aka, “DI”) is a natural technique of composing objects in OOP (known long before the term was introduced by Martin Fowler), Spring IoC, Google Guice, Java EE6 CDI, Dagger and other DI frameworks turn it into an anti-pattern.
I’m not going to discuss obvious arguments against “setter injections” (like in Spring IoC) and “field injections” (like in PicoContainer). These mechanisms simply violate basic principles of object-oriented programming and encourage us to create incomplete, mutable objects, that get stuffed with data during the course of application execution. Remember: ideal objects must be immutable and may not contain setters.
Instead, let’s talk about “constructor injection” (like in Google Guice) and its use with dependency injection containers. I’ll try to show why I consider these containers a redundancy, at least.
What is Dependency Injection?
This is what dependency injection is (not really different from a plain old object composition):
public class Budget { private final DB db; public Budget(DB data) { this.db = data; } public long total() { return this.db.cell( "SELECT SUM(cost) FROM ledger" ); } }
The object data
is called a “dependency”.
A Budget
doesn’t know what kind of database it is working with. All it needs from the database is its ability to fetch a cell, using an arbitrary SQL query, via method cell()
. We can instantiate a Budget
with a PostgreSQL implementation of the DB
interface, for example:
public class App { public static void main(String... args) { Budget budget = new Budget( new Postgres("jdbc:postgresql:5740/main") ); System.out.println("Total is: " + budget.total()); } }
In other words, we’re “injecting” a dependency into a new object budget
.
An alternative to this “dependency injection” approach would be to let Budget
decide what database it wants to work with:
public class Budget { private final DB db = new Postgres("jdbc:postgresql:5740/main"); // class methods }
This is very dirty and leads to 1) code duplication, 2) inability to reuse, and 3) inability to test, etc. No need to discuss why. It’s obvious.
Thus, dependency injection via a constructor is an amazing technique. Well, not even a technique, really. More like a feature of Java and all other object-oriented languages. It’s expected that almost any object will want to encapsulate some knowledge (aka, a “state”). That’s what constructors are for.
What is a DI Container?
So far so good, but here comes the dark side — a dependency injection container. Here is how it works (let’s use Google Guice as an example):
import javax.inject.Inject; public class Budget { private final DB db; @Inject public Budget(DB data) { this.db = data; } // same methods as above }
Pay attention: the constructor is annotated with @Inject
.
Then, we’re supposed to configure a container somewhere, when the application starts:
Injector injector = Guice.createInjector( new AbstractModule() { @Override public void configure() { this.bind(DB.class).toInstance( new Postgres("jdbc:postgresql:5740/main") ); } } );
Some frameworks even allow us to configure the injector in an XML file.
From now on, we are not allowed to instantiate Budget
through the new
operator, like we did before. Instead, we should use the injector we just created:
public class App { public static void main(String... args) { Injection injector = // as we just did in the previous snippet Budget budget = injector.getInstance(Budget.class); System.out.println("Total is: " + budget.total()); } }
The injection automatically finds out that in order to instantiate a Budget
it has to provide an argument for its constructor. It will use an instance of class Postgres
, which we instantiated in the injector.
This is the right and recommended way to use Guice. There are a few even darker patterns, though, which are possible but not recommended. For example, you can make your injector a singleton and use it right inside the Budget
class. These mechanisms are considered wrong even by DI container makers, however, so let’s ignore them and focus on the recommended scenario.
What Is This For?
Let me reiterate and summarize the scenarios of incorrect usage of dependency injection containers:
- Field injection
- Setter injection
- Passing injector as a dependency
- Making injector a global singleton
If we put all of them aside, all we have left is the constructor injection explained above. And how does that help us? Why do we need it? Why can’t we use plain old new
in the main class of the application?
The container we created simply adds more lines to the code base, or even more files, if we use XML. And it doesn’t add anything, except an additional complexity. We should always remember this if we have the question: “What database is used as an argument of a Budget?”
The Right Way
Now, let me show you a real life example of using new
to construct an application. This is how we create a “thinking engine” in rultor.com (full class is in Agents.java
):
final Agent agent = new Agent.Iterative( new Array( new Understands( this.github, new QnSince( 49092213, new QnReferredTo( this.github.users().self().login(), new QnParametrized( new Question.FirstOf( new Array( new QnIfContains("config", new QnConfig(profile)), new QnIfContains("status", new QnStatus(talk)), new QnIfContains("version", new QnVersion()), new QnIfContains("hello", new QnHello()), new QnIfCollaborator( new QnAlone( talk, locks, new Question.FirstOf( new Array( new QnIfContains( "merge", new QnAskedBy( profile, Agents.commanders("merge"), new QnMerge() ) ), new QnIfContains( "deploy", new QnAskedBy( profile, Agents.commanders("deploy"), new QnDeploy() ) ), new QnIfContains( "release", new QnAskedBy( profile, Agents.commanders("release"), new QnRelease() ) ) ) ) ) ) ) ) ) ) ) ), new StartsRequest(profile), new RegistersShell( "b1.rultor.com", 22, "rultor", IOUtils.toString( this.getClass().getResourceAsStream("rultor.key"), CharEncoding.UTF_8 ) ), new StartsDaemon(profile), new KillsDaemon(TimeUnit.HOURS.toMinutes(2L)), new EndsDaemon(), new EndsRequest(), new Tweets( this.github, new OAuthTwitter( Manifests.read("Rultor-TwitterKey"), Manifests.read("Rultor-TwitterSecret"), Manifests.read("Rultor-TwitterToken"), Manifests.read("Rultor-TwitterTokenSecret") ) ), new CommentsTag(this.github), new Reports(this.github), new RemovesShell(), new ArchivesDaemon( new ReRegion( new Region.Simple( Manifests.read("Rultor-S3Key"), Manifests.read("Rultor-S3Secret") ) ).bucket(Manifests.read("Rultor-S3Bucket")) ), new Publishes(profile) ) );
Impressive? This is a true object composition. I believe this is how a proper object-oriented application should be instantiated.
And DI containers? In my opinion, they just add unnecessary noise.
Related Posts
You may also find these posts interesting:
- Getters/Setters. Evil. Period.
- Anti-Patterns in OOP
- Avoid String Concatenation
- Objects Should Be Immutable
- Why NULL is Bad?
Reference: | DI Containers are Code Polluters from our JCG partner Yegor Bugayenko at the About Programming blog. |
Sorry, but I just think that you reinvented the wheel with an unreadable solution, and that you are labeling all other solutions as “incorrect usage”. But what disturbs me the most, is that all of this is presented as facts. Those are just made-up claims, religious dogmas without academic backing.
Totally agree. This is one of the worst article that I have ever read in this website. Unreadable code, unproductive.
“The Right Way” LOL
Thanks for reading :) Which one of that four “incorrect usage” scenarios you find correct?
Field injection is totally valid, as Martin said in a service layer. Guess you hate that? This doesn’t make it wrong, and your example is not the unique “right way”.
Other types of injections have their pro/cons.
Why wouldn’t you present your opinion as such, rather than facts?
Well, let’s try to prove it (using logic). 1. Encapsulation is the main principle of OOP, and it means that nobody can access object’s state except its own methods. If this principle is violated we don’t have an OOP any more. It’s a fact. Right? 2. In order to implement field injection a DI container must break encapsulation using Java Reflection. It’s a fact. Right? 3. Thus, field injection violates the encapsulation principle of OOP.
It’s not my opinion. It’s a proven fact. Make sense?
Seems to me you are mixing up the concept of OOP and the Java language. One is a concept and the other a concrete implementation, e.g. the term “interface” in OOP could be implemented as an abstract class in Java.
As for field injection you annotate your fields with @Inject or @Autowire etc. You do that for simplification by creating a “setter-like” idiom.
So, no, that does not break encapsulation.
I don’t agree. You still think your opinion is an universal fact.
1) Breaking encapsulation outside of your logic does not make your code non OO. It justs makes your code managed by a container which breaks your code’s encapsultaiton.
2) Having parts which aren’t OO doesn’t make the rest non OO. OO for the sake of it could make your code bloated.
Your example: Impressive? Yes. Better? Matter of tase. But your example is DSL for job configuration where CI use would be really inappropriate, no doubts about that.
Problem with CI is same as many others programming problems. It is hugely overused and applied when it should not be, just because it is so easy.
CI makes perfect sense for service layer where Transactional and Security concerns makes gluing everything together with “new” simply uneconomical.
“DSL for job configuration where CI use would be really inappropriate” should he not use a builder class here?
I guess the idea is that service layers are bad anyway, and that this example is thus universal ;-)
You’re right about Transactional or Security or Logging concerns. Let’s call them “aspects” instead and you immediately understand that we should be using AOP (Aspect Oriented Programming). Indeed, it would be very un-economical to pass an instance of logger through all constructors. AOP helps us to minimize this footprint. Check some of my articles on this subject: http://www.yegor256.com/tag/aop.html
Well, that’s a good point in favor of DI: most DI frameworks as well as the standard ones (CDI / Java EE) provides injected dependencies and AOP. They do more than pure injection.
Dependencies between services, external resources etc. can be handled by field injection, and that will make it simple. Is it pure OOP? How do you define that btw? Same as for the discussion we had about dumb structures: you shouldn’t be forced to refrain from using simple, effective ways of coding just because your reading of OOP (any academic backing?) says you can’t.
“Let me reiterate and summarize the scenarios of incorrect usage of dependency injection containers:
Field injection
Setter injection
Passing injector as a dependency
Making injector a global singleton”
Why is Making injector a global singleton an incorrect usage?
I think it will let you have the application config in single place to readily answer questions like “What database is used as an argument of a Budget?”
Singleton is a famous anti-pattern. See http://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons
That seems to be about doing SomeClass.getInstance() in some code vs new SomeClass() or passing some class as a reference. It has nothing to do wit swaraj’s comment about having a single place to decide what is passed to an instance of Budget.
this definitely looks like new pattern emerged: dependency infection injection (DII) – *LOVE IT!*
I agree with everything you said right up until your presented your beast of an injector. I think it should be broken down into methods that provide the objects to inject in the same way that the DIY-DI method suggests. It’s more code, but it becomes readable code, and able to be debugged much more easily. Your pile is really difficult to look at and follow, which can easily lead to bugs.
You’re on the right track, but you can make your code cleaner.
Ok, now instantiate that object in two different places.
Boom. You’ve just discovered why factories exist. Double boom, you’ve just discovered IoC / DI containers exist (hint – they’re centralized abstract factories).
Also, that object of yours clearly has too many responsibilities.
I agree with you and I second the comments that your example could use come clean-up. DI is a great pattern, but it totally breaks the very concept of encapsulation. Why should an innocent pojo be burdened with hard coded knowledge about a specific technical implementation. I would take it a step further and say that annotation abuse has made it even worse.