Enterprise Java

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:

Reference: DI Containers are Code Polluters from our JCG partner Yegor Bugayenko at the About Programming blog.

Yegor Bugayenko

Yegor Bugayenko is an Oracle certified Java architect, CEO of Zerocracy, author of Elegant Objects book series about object-oriented programing, lead architect and founder of Cactoos, Takes, Rultor and Jcabi, and a big fan of test automation.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

19 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Yannick Majoros
Yannick Majoros
9 years ago

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.

Enrique Fernández-Polo
Enrique Fernández-Polo
9 years ago

Totally agree. This is one of the worst article that I have ever read in this website. Unreadable code, unproductive.

“The Right Way” LOL

Yegor Bugayenko
9 years ago

Thanks for reading :) Which one of that four “incorrect usage” scenarios you find correct?

Yannick Majoros
Yannick Majoros
9 years ago

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?

Yegor Bugayenko
9 years ago

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?

Hendrik
Hendrik
9 years ago

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.

Yannick Majoros
Yannick Majoros
9 years ago

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.

Martin Vanek
Martin Vanek
9 years ago

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.

swaraj yadav
swaraj yadav
9 years ago
Reply to  Martin Vanek

“DSL for job configuration where CI use would be really inappropriate” should he not use a builder class here?

Yannick Majoros
Yannick Majoros
9 years ago
Reply to  Martin Vanek

I guess the idea is that service layers are bad anyway, and that this example is thus universal ;-)

Yegor Bugayenko
9 years ago
Reply to  Martin Vanek

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

Yannick Majoros
Yannick Majoros
9 years ago

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.

swaraj yadav
swaraj yadav
9 years ago

“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?”

Yegor Bugayenko
9 years ago
Reply to  swaraj yadav
Ben
Ben
9 years ago

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.

YF
YF
9 years ago

this definitely looks like new pattern emerged: dependency infection injection (DII) – *LOVE IT!*

Jacob Zimmerman
9 years ago

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.

Jon
Jon
8 years ago

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.

agr
agr
8 years ago

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.

Back to top button