8

The currently most upvoted answer to a very recent question states that

DI containers are an "enterprise software" pattern, used when the object graph is very large and complex. I suspect that 95% of applications do not require it.

which is something I strongly disagree with. Maybe I've got the terminology wrong, but for me DI framework means just "something wiring my objects together". Am I missing something?

I'm using Guice even for really tiny projects (like 10 classes) in order to simplify them. Sure, it's a 400 kB JAR file, but this isn't what I'd care about. For a small project I hardly ever need any configuration and the only "overhead" is adding the @Inject annotation.

So I really wonder, what added complexity do DI frameworks cause?

Update addressing the answers

In a 82 class project, I have

  • 32 @Inject annotations
  • 15 @Singleton and 1 @ProvidedBy annotations
  • 4 Providers (all of which I'd need also without DI as they're my factories)
  • 1 Module containing a single line
  • 0 lines XML !!!

That's all. Sure, it's small project, but exactly this was my point. The additional work was a few words rather than lines.

I'm using solely constructor injection in order to get immutable "easygoing" objects. Whenever a new dependency appears, I add a final field, let Lombok's RequiredArgsConstructor take care of the declaration, and let Guice take care of calling it properly.

3
  • is Guice doing property injection?
    – Reactgular
    Jun 15, 2014 at 22:36
  • @MathewFoscarini I guess so - I know other terms and this should be (a special case of) the method injection.
    – maaartinus
    Jun 15, 2014 at 23:14
  • 1
    I am so thankful for Guice. It has simplified a lot of my development and made it more testable and robust. I cannot believe that I went without DI for so long. The cake is real. Apr 11, 2015 at 14:48

4 Answers 4

6

There are a couple of compromises that are to be made when using DI frameworks as far as I can see.

The most worrying for me is that your application code is usually spread between a main programming language (like Java) and XML/JSON configuration code (it is code). This means that in the case of problems with your application you need to look in two places. Often the configuration code is not easily related to the main application code.

Of course the configuration is also outside the bounds of what the compiler (and often the IDE) can check for you, meaning that it is much easier to make mistakes in this configuration than if you were writing a main class that handled the wiring. In effect this means that wiring issues are pushed from being compile-time issues to being runtime issues.

As well as splitting the application, using DI Frameworks also often mean that you use @Inject or similar within your code, instead of traditional DI techniques (CTOR interface injection in Java/C++, template injection in C++). The downside of this is that you must then use a DI framework with the application. An alternative would be to design your application without expecting a DI framework and then allow the DI framework to re-use the traditional CTOR/setter injection of your classes.

The downside to the traditional injection mechanism comes when a complex and properly encapsulation classes demands a number of other relatively complex injections at CTOR time. This is usually solved by incorporating a Builder, but it can be a pain.

EDIT:

The lads below have mentioned that Guice does not need a separate XML/JSON config. My answer really applies to my own usage of Spring.

4
  • 2
    The nice thing about Guice is that it can be fully configured in Java (there's no XML unless you get an add-on). For small projects (which is what I'masking about), "auto-wiring" actually suffices. I've updated my question; maybe all the fears come from bad (or too enterpricey DI frameworks)?
    – maaartinus
    Jun 17, 2014 at 20:42
  • 1
    @Dennis: Guice isn't intrusive at all. The Guice container is merely an alternative way to get object instances. You can still call constructors if you like. That is handy for unit testing with mocked dependencies. Jun 17, 2014 at 22:07
  • 1
    Can't speak to Guice in particular, but a lot of DI stacks are very automatic if you just use them for things that have long lifecycles -- singletons, factories, and the like. These types of objects with constructor injection let you get away from static things and hard dependencies pretty easily; and I find it to be useful for those cases even for tiny projects. I would say that, if you are using the simpler features and constructor injection of an DI framework, and it adds a lot of complexity... find a new DI framework.
    – danwyand
    Jun 17, 2014 at 23:42
  • 1
    @maaartinus - You could be right about that. I'm not familiar with Guice to be honest so I suppose that my answer applies mostly to Spring.
    – Dennis
    Jun 18, 2014 at 9:51
4

I think the main disadvantage of using an IoC container is all the magic it does behind the scenes. (This is also a problem with ORMs, another common magical tool.) Debugging IoC issues is not fun, because it becomes harder to see how and when dependencies are being fulfilled. You can't step through dependency resolution in the debugger.

Containers often make it awkward to configure "class A with one version of a dependency, and class B with a different version", or similar scenarios where abstractions are reused. You can end up in situations where every class has its own interface, even when they mean the same thing. This blocks you from the abstraction power of reusing interfaces.

In my opinion, the really big win you get from IoC is automatic lifestyle management. You can declare components as singletons, per-request singletons, transient instances, and so on - and the container takes care of it all for you. It's considerably harder to set this up using new-expressions. On the other hand, it's still easy to cause resource leaks by mismatching lifestyles.

Some projects use IoC to construct everything in the system - even classes representing business data - with no new keywords. I think this is generally a mistake because it encourages you to write poorly-encapsulated classes and too many interfaces. Your code becomes reliant on IoC. It also seems to be co-morbid with over-testing.

Personally, I generally prefer to go without a container. Instead, I compose objects using new-expressions encapsulated in a composition root. It's easy to see the concrete dependencies of a given class, and when I change a constructor I get compile-time errors, both of which are big maintenance wins. It's also straightforward to to configure separate instances with different components.

My advice, if you want to use an IoC container, is to fulfil your dependencies at the entry point to the system, and only use it for your actual dependencies (repositories et al). And remember you can still use dependency injection, and even keep configuration in a single file, without using a container.

1
  • 1
    "remember you can still use dependency injection, and even keep configuration in a single file, without using a container." ++. I think too many people forget that DI and DI container frameworks are not the same thing.
    – RubberDuck
    Apr 11, 2015 at 15:27
2

Some developers argue that any framework you use in your code is a potential problem at a later date, because it typically requires you to write your code in a certain way to interact with it correctly, and this can cause issues at a later date if it turns out that those ways conflict with other design requirements. An example of this concern is expressed by Bob Martin here. I don't personally agree, but I can see his point.

1
  • 2
    So the benefits gained by using this framework in a more complex project may make it worth the risk?
    – JeffO
    Jun 17, 2014 at 20:56
1

To summarize: It looks like the only complexity comes from using improper configuration methods (yes, XML is not type-safe) and possibly also from frameworks doing much more work (Spring manages the object lifecycle, which goes far beyond DI).

For a simple project, DI doing right is extremely useful:

  • it allows to write services as tiny immutable classes with minimum effort
  • it ensures singletonness in the right scope while staying testable
  • it needs hardly any configuration (about 1 line per 20 DI classes)

It's also pretty harmless, as it all could be done manually. It's just creating the instances, passing them to constructors in order to create more instance, and so on. Boring and long, but simple.

A typical service using Guice and Lombok looks like this

@Singleton
@RequiredArgsConstructor(onConstructor=@__(@Inject))
public class TotalCostsComputer {
    public CostsResult getCosts(Goods goods, Shippment shippment) {
        return taxComputer.applyTax(composeResult(
                    goodsCostComputer.getCosts(goods),
                    shippmentCostComputer.getCosts(shippment));
    }
    ...

    private final GoodsCostComputer goodsCostComputer;
    private final ShippmentCostComputer shippmentCostComputer;
    private final TaxComputer taxComputer;
}

Now, adding a dependency means just adding a new private final field. It can't be null (Guice guarantees this even without @NonNull).

Testing means either creating your own instances or getting them from the Injector (possibly configured to return some stubs). Both is pretty simple.

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.