2

Sometimes we can do some neat tricks with our DI Container, for example: auto-binding, managing singletons, managing one-instance-per-request etc. This is great, and can really simplify some scenarios.

The problem I have with this is that a particular class's concerns now get leaked to the application layer. If a class expects to be instantiated and managed in a particular way (e.g. as a singleton, or only once per http request), it is now up to the application layer to ensure that this occurs.

Some problems that occur:

1) Potential bugs, as the application could incorrectly setup the DI bindings.

2) It can be confusing when a developer wants to implement a package, as the rules for setting up the DI container are not provided by the package itself, and so instead must be documented in comments or accompanying test-cases (which is not ideal).

3) If the implementation of a class changes, it is now the responsibility of every application that uses the class to update their DI container bindings.

Here are some example bindings that you can do using NInject that all exhibit this problem:

public class MyApplicationsInjectionModule : NInjectModule
{
    public void Load()
    {
        Bind<IFoo>().ToConstant(FooThatShouldBeASingleton.Instant);

        Bind<IFoo>().To<FooThatShouldBeASingleton>().AsSingleton();

        Bind<IFoo>().To<FooThatShouldOnlyBeInstantiatedOncePerRequest>().InRequestScope();
    }
}

My experience is only with NInject - perhaps some other DI containers deal with this problem more elegantly.

What strategies can we take to avoid these problems, without giving up the power that a DI container provides?

cbp
  • 25,252
  • 29
  • 125
  • 205

1 Answers1

2

Dependency Injection introduces flexibility, and with that flexibility also comes an added risk of incorrectly composing object graphs. This is a one of the (very) few disadvantages of DI, and it has nothing to do with whether or not you use a DI Container.

1) Potential bugs, as the application could incorrectly setup the DI bindings.

Even with Poor Man's DI it's perfectly possible to compose an incorrect object graph.

2) It can be confusing when a developer wants to implement a package, as the rules for setting up the DI container are not provided by the package itself, and so instead must be documented in comments or accompanying test-cases (which is not ideal).

This is why the best strategy (on .NET) is to adopt Convention-based Configuration. Basically, you can tell a container to scan all appropriate assemblies and register all public classes against the interfaces they implement.

If you stick to Constructor Injection, Auto-wiring will take care of the rest for you.

As an example, let's say that you have this class defined in an assembly:

public class Foo : IFoo
{
    private readonly IBar bar;

    public Foo(IBar bar)
    {
        if (bar == null)
            throw new ArgumentNullException("bar");

        this.bar = bar;
    }

    // Use this.bar for something interesting in the class...
}

In another assembly, you may have

public class Bar : IBar { }

A container which is configured to scan appropriate assemblies will find Bar and register it as IBar, as well as it will find Foo and register it against IFoo. Since the Bar constructor statically advertises its required dependencies, Auto-wiring can kick in and the container will be able to automatically resolve IFoo.

With proper conventions in place, you don't need to reconfigure the container to add new types to your code base.

3) If the implementation of a class changes, it is now the responsibility of every application that uses the class to update their DI container bindings.

Again, if you use Convention over Configuration, this will happen automatically.

Community
  • 1
  • 1
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • OK, so you're basically saying: Keep it simple - just don't try anything tricky. – cbp Nov 24 '11 at 01:02