This post explains why a DI Container is useful with Convention over Configuration while Poor Man's DI might be a better fit for a more explicit Composition Root.

It seems to me that lately there's been a backlash against DI Containers among alpha geeks. Many of the software leaders that I myself learn from seem to dismiss the entire concept of a DI Container, claiming that it's too complex, too 'magical', that it isn't a good architectural pattern, or that the derived value doesn't warrant the 'cost' (most, if not all, DI Containers are open source, so they are free in a monetary sense, but there's always a cost in learning curve etc.).

This must have caused Krzysztof Ko┼║mic to write a nice article about what sort of problem a DI Container solves. I agree with the article, but want to provide a different perspective here.

In short, it makes sense to me to illustrate the tradeoffs of Poor Man's DI versus DI Containers in a diagram like this:

usefulness vs. sophistication

The point of the diagram is that Poor Man's DI can be valuable because it's simple, while a DI Container can be either valuable or pointless depending on how it's used. However, when used in a sufficiently sophisticated way I consider a DI Container to offer the best value/cost ratio. When people criticize DI Containers as being pointless I suspect that what really happened was that they gave up before they were out of the Trough of Disillusionment. Had they continued to learn, they might have arrived at a new Plateau of Productivity.

DI style Advantages Disadvantages
Poor Man's DI
  • Easy to learn
  • Strongly typed
  • High maintenance
Explicit Register
  • Weakly typed
Convention over Configuration
  • Low maintenance
  • Hard to learn
  • Weakly typed

There are other, less important advantages and disadvantages of each approach, but here I'm focusing on three main axes that I consider important:

  • How easy is it to understand and learn?
  • How soon will you get feedback if something is not right?
  • How easy is it to maintain?

The major advantage of Poor Man's DI is that it's easy to learn. You don't have to learn the API of any DI Container (Unity, Autofac, Ninject, StructureMap, Castle Windsor, etc.) and while individual classes still use DI, once you find the Composition Root it'll be evident what's going on and how object graphs are constructed. No 'magic' is involved.

The second big advantage of Poor Man's DI is often overlooked: it's strongly typed. This is an advantage because it provides the fastest feedback about correctness that you can get. However, strong typing cuts both ways because it also means that every time you refactor a constructor, you will break the Composition Root. If you are sharing a library (Domain Model, Utility, Data Access component, etc.) between more than one application (unit of deployment), you may have more than one Composition Root to maintain. How much of a burden this is depends on how often you refactor constructors, but I've seen projects where this happens several times each day (keep in mind that constructor are implementation details).

If you use a DI Container, but explicitly Register each and every component using the container's API, you lose the rapid feedback from strong typing. On the other hand, the maintenance burden is also likely to drop because of Auto-wiring. Still, you'll need to register each new class or interface when you introduce them, and you (and your team) still has to learn the specific API of that container. In my opinion, you lose more advantages than you gain.

Ultimately, if you can wield a DI Container in a sufficiently sophisticated way, you can use it to define a set of conventions. These conventions define a rule set that your code should adhere to, and as long as you stick to those rules, things just work. The container drops to the background, and you rarely need to touch it. Yes, this is hard to learn, and is still weakly typed, but if done right, it enables you to focus on code that adds value instead of infrastructure. An additional advantage is that it creates a positive feedback mechanism forcing a team to produce code that is consistent with the conventions.

Example: Poor Man's DI

The following example is part of my Booking sample application. It shows the state of the Ploeh.Samples.Booking.Daemon.Program class as it looks in the git tag total-complexity (git commit ID 64b7b670fff9560d8947dd133ae54779d867a451).

var queueDirectory = 
    new DirectoryInfo(@"..\..\..\BookingWebUI\Queue").CreateIfAbsent();
var singleSourceOfTruthDirectory = 
    new DirectoryInfo(@"..\..\..\BookingWebUI\SSoT").CreateIfAbsent();
var viewStoreDirectory = 
    new DirectoryInfo(@"..\..\..\BookingWebUI\ViewStore").CreateIfAbsent();
 
var extension = "txt";
 
var fileDateStore = new FileDateStore(
    singleSourceOfTruthDirectory,
    extension);
 
var quickenings = new IQuickening[]
{
    new RequestReservationCommand.Quickening(),
    new ReservationAcceptedEvent.Quickening(),
    new ReservationRejectedEvent.Quickening(),
    new CapacityReservedEvent.Quickening(),
    new SoldOutEvent.Quickening()
};
 
var disposable = new CompositeDisposable();
var messageDispatcher = new Subject<object>();
disposable.Add(
    messageDispatcher.Subscribe(
        new Dispatcher<RequestReservationCommand>(
            new CapacityGate(
                new JsonCapacityRepository(
                    fileDateStore,
                    fileDateStore,
                    quickenings),
                new JsonChannel<ReservationAcceptedEvent>(
                    new FileQueueWriter<ReservationAcceptedEvent>(
                        queueDirectory,
                        extension)),
                new JsonChannel<ReservationRejectedEvent>(
                    new FileQueueWriter<ReservationRejectedEvent>(
                        queueDirectory,
                        extension)),
                new JsonChannel<SoldOutEvent>(
                    new FileQueueWriter<SoldOutEvent>(
                        queueDirectory,
                        extension))))));
disposable.Add(
    messageDispatcher.Subscribe(
        new Dispatcher<SoldOutEvent>(
            new MonthViewUpdater(
                new FileMonthViewStore(
                    viewStoreDirectory,
                    extension)))));
 
var q = new QueueConsumer(
    new FileQueue(
        queueDirectory,
        extension),
    new JsonStreamObserver(
        quickenings,
        messageDispatcher));
 
RunUntilStopped(q);

Yes, that's a lot of code. I deliberately chose a non-trivial example to highlight just how much stuff there might be. You don't have to read and understand all of this code to appreciate that it might require a bit of maintenance. It's a big object graph, with some shared subgraphs, and since it uses the new keyword to create all the objects, every time you change a constructor signature, you'll need to update this code, because it's not going to compile until you do.

Still, there's no 'magical' tool (read: DI Container) involved, so it's pretty easy to understand what's going on here. As Dan North put it once I saw him endorse this technique: 'new' is the new 'new' :) Once you see how Explicit Register looks, you may appreciate why.

Example: Explicit Register

The following example performs exactly the same work as the previous example, but now in a state (git tag: controllers-by-convention; commit ID: 13fc576b729cdddd5ec53f1db907ec0a7d00836b) where it's being wired by Castle Windsor. The name of this class is DaemonWindsorInstaller, and all components are explictly registered. Hang on to something.

container.Register(Component
    .For<DirectoryInfo>()
    .UsingFactoryMethod(() =>
        new DirectoryInfo(@"..\..\..\BookingWebUI\Queue").CreateIfAbsent())
    .Named("queueDirectory"));
container.Register(Component
    .For<DirectoryInfo>()
    .UsingFactoryMethod(() =>
        new DirectoryInfo(@"..\..\..\BookingWebUI\SSoT").CreateIfAbsent())
    .Named("ssotDirectory"));
container.Register(Component
    .For<DirectoryInfo>()
    .UsingFactoryMethod(() =>
        new DirectoryInfo(@"..\..\..\BookingWebUI\ViewStore").CreateIfAbsent())
    .Named("viewStoreDirectory"));            
 
container.Register(Component
    .For<IQueue>()
    .ImplementedBy<FileQueue>()
    .DependsOn(
        Dependency.OnComponent("directory", "queueDirectory"),
        Dependency.OnValue("extension", "txt")));
 
container.Register(Component
    .For<IStoreWriter<DateTime>, IStoreReader<DateTime>>()
    .ImplementedBy<FileDateStore>()
    .DependsOn(
        Dependency.OnComponent("directory", "ssotDirectory"),
        Dependency.OnValue("extension", "txt")));
container.Register(Component
    .For<IStoreWriter<ReservationAcceptedEvent>>()
    .ImplementedBy<FileQueueWriter<ReservationAcceptedEvent>>()
    .DependsOn(
        Dependency.OnComponent("directory", "queueDirectory"),
        Dependency.OnValue("extension", "txt")));
container.Register(Component
    .For<IStoreWriter<ReservationRejectedEvent>>()
    .ImplementedBy<FileQueueWriter<ReservationRejectedEvent>>()
    .DependsOn(
        Dependency.OnComponent("directory", "queueDirectory"),
        Dependency.OnValue("extension", "txt")));
container.Register(Component
    .For<IStoreWriter<SoldOutEvent>>()
    .ImplementedBy<FileQueueWriter<SoldOutEvent>>()
    .DependsOn(
        Dependency.OnComponent("directory", "queueDirectory"),
        Dependency.OnValue("extension", "txt")));
 
container.Register(Component
    .For<IChannel<ReservationAcceptedEvent>>()
    .ImplementedBy<JsonChannel<ReservationAcceptedEvent>>());
container.Register(Component
    .For<IChannel<ReservationRejectedEvent>>()
    .ImplementedBy<JsonChannel<ReservationRejectedEvent>>());
container.Register(Component
    .For<IChannel<SoldOutEvent>>()
    .ImplementedBy<JsonChannel<SoldOutEvent>>());
 
container.Register(Component
    .For<ICapacityRepository>()
    .ImplementedBy<JsonCapacityRepository>());
 
container.Register(Component
    .For<IConsumer<RequestReservationCommand>>()
    .ImplementedBy<CapacityGate>());
container.Register(Component
    .For<IConsumer<SoldOutEvent>>()
    .ImplementedBy<MonthViewUpdater>());
 
container.Register(Component
    .For<Dispatcher<RequestReservationCommand>>());
container.Register(Component
    .For<Dispatcher<SoldOutEvent>>());
 
container.Register(Component
    .For<IObserver<Stream>>()
    .ImplementedBy<JsonStreamObserver>());
container.Register(Component
    .For<IObserver<DateTime>>()
    .ImplementedBy<FileMonthViewStore>()
    .DependsOn(
        Dependency.OnComponent("directory", "viewStoreDirectory"),
        Dependency.OnValue("extension", "txt")));
container.Register(Component
    .For<IObserver<object>>()
    .UsingFactoryMethod(k =>
    {
        var messageDispatcher = new Subject<object>();
        messageDispatcher.Subscribe(k.Resolve<Dispatcher<RequestReservationCommand>>());
        messageDispatcher.Subscribe(k.Resolve<Dispatcher<SoldOutEvent>>());
        return messageDispatcher;
    }));
 
container.Register(Component
    .For<IQuickening>()
    .ImplementedBy<RequestReservationCommand.Quickening>());
container.Register(Component
    .For<IQuickening>()
    .ImplementedBy<ReservationAcceptedEvent.Quickening>());
container.Register(Component
    .For<IQuickening>()
    .ImplementedBy<ReservationRejectedEvent.Quickening>());
container.Register(Component
    .For<IQuickening>()
    .ImplementedBy<CapacityReservedEvent.Quickening>());
container.Register(Component
    .For<IQuickening>()
    .ImplementedBy<SoldOutEvent.Quickening>());
 
container.Register(Component
    .For<QueueConsumer>());
 
container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));

This is actually more verbose than before - almost double the size of the Poor Man's DI example. To add spite to injury, this is no longer strongly typed in the sense that you'll no longer get any compiler errors if you change something, but a change to your classes can easily lead to a runtime exception, since something may not be correctly configured.

This example uses the Registration API of Castle Windsor, but imagine the horror if you were to use XML configuration instead.

Other DI Containers have similar Registration APIs (apart from those that only support XML), so this problem isn't isolated to Castle Windsor only. It's inherent in the Explicit Register style.

I can't claim to be an expert in Java, but all I've ever heard and seen of DI Containers in Java (Spring, Guice, Pico), they don't seem to have Registration APIs much more sophisticated than that. In fact, many of them still seem to be heavily focused on XML Registration. If that's the case, it's no wonder many software thought leaders (like Dan North with his 'new' is the new 'new' line) dismiss DI Containers as being essentially pointless. If there weren't a more sophisticated option, I would tend to agree.

Example: Convention over Configuration

This is still the same example as before, but now in a state (git tag: services-by-convention-in-daemon; git commit ID: 0a7e6f246cacdbefc8f6933fc84b024774d02038) where almost the entire configuration is done by convention.

container.AddFacility<ConsumerConvention>();
 
container.Register(Component
    .For<IObserver<object>>()
    .ImplementedBy<CompositeObserver<object>>());
 
container.Register(Classes
    .FromAssemblyInDirectory(new AssemblyFilter(".").FilterByName(an => an.Name.StartsWith("Ploeh.Samples.Booking")))
    .Where(t => !(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dispatcher<>)))
    .WithServiceAllInterfaces());
 
container.Kernel.Resolver.AddSubResolver(new ExtensionConvention());
container.Kernel.Resolver.AddSubResolver(new DirectoryConvention(container.Kernel));
container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));
 
#region Manual configuration that requires maintenance
container.Register(Component
    .For<DirectoryInfo>()
    .UsingFactoryMethod(() =>
        new DirectoryInfo(@"..\..\..\BookingWebUI\Queue").CreateIfAbsent())
    .Named("queueDirectory"));
container.Register(Component
    .For<DirectoryInfo>()
    .UsingFactoryMethod(() =>
        new DirectoryInfo(@"..\..\..\BookingWebUI\SSoT").CreateIfAbsent())
    .Named("ssotDirectory"));
container.Register(Component
    .For<DirectoryInfo>()
    .UsingFactoryMethod(() =>
        new DirectoryInfo(@"..\..\..\BookingWebUI\ViewStore").CreateIfAbsent())
    .Named("viewStoreDirectory"));
#endregion

It's pretty clear that this is a lot less verbose - and then I even left three explicit Register statements as a deliberate decision. Just because you decide to use Convention over Configuration doesn't mean that you have to stick to this principle 100 %.

Compared to the previous example, this requires a lot less maintenance. While you are working with this code base, most of the time you can concentrate on adding new functionality to the software, and the conventions are just going to pick up your changes and new classes and interfaces. Personally, this is where I find the best tradeoff between the value provided by a DI Container versus the cost of figuring out how to implement the conventions. You should also keep in mind that once you've learned to use a particular DI Container like this, the cost goes down.

Summary

Using a DI Container to compose object graphs by convention presents an unparalled opportunity to push infrastructure code to the background. However, if you're not prepared to go all the way, Poor Man's DI may actually be a better option. Don't use a DI Container just to use one. Understand the value and cost associated with it, and always keep in mind that Poor Man's DI is a valid alternative.


Comments

I'd offer a different suggestion: Try writing a test for every line of code you write. Then tell me how Poor Man's DI works out for you.

Testing usually seems to be the pressure release for not using an IoC container in a static language. Even if it's just a matter of not writing the tests for the overridden constructor, testing is usually thrown out. And in a world without tests, Poor Man's DI (or not DI at all) is often the "simpler" solution. Less lines of code, "it just works," etc etc. There are lots of options when you only look at the implementation without concern about how one is to provide automated verification against regressions.

If using TDD or even just "testing," an IoC container is always the simpler solution. Unless, of course -- if you just switch to a language or framework that lets you do both. *cough* ruby *cough* python *cough* dynamic languages *cough*
2012-11-06 17:00 UTC
Daniel Hilgarth
Very good article, thank you!


Just to add another perspective:

I created some extension methods in one of my core libraries that registers everything that ends with Service, Factory, Provider etc. Additionally, I created some extension methods for special areas like NHibernate or AutoMapping.

With these extension methods and a project that adheres to these conventions, my composition roots are very short and need virtually no maintenance.

I have successfully used this approach in several mid to big sized projects. It just works, I wouldn't want to work without a DI container anymore as it would cost so much more time.
2012-11-06 19:53 UTC
Darren, thank you for your comment. How do you think TDD fits into this discussion? Which overloaded constructors? If you examine the git repo behind these samples, you should find that I didn't change the production code between the three examples. It's the same production code - the same classes, the same constructors, etc. - just wired in three different ways.
2012-11-06 20:52 UTC
Every time I've heard of Poor Man's DI, it's always described the practice of creating two constructors:

1.) A constructor with no arguments. Dependencies are initialized in the constructor. This constructor can be used to instantiate the object like "myThing = new MyThing();" This is not using DI at all.

2.) A constructor with arguments for each dependency. Dependencies are passed in. This constructor is used for testing, since it actually uses DI.

This "Poor Man's DI" is a concept because it's a cheap way to get DI into a class that may not have originally been written to support DI. In a way, it seems to give devs the best of both worlds. Users can still instantiate the class simply, but users can also test it. It sounds fine, but it has some issues because the class is still bound to its dependencies and because the implementation uses different code than the tests.

Looking deeper at your example, I see that's not what your "Poor Man's DI" example is. Your way is fully testable, but I don't think its deserving of the extra "Poor Man's DI" moniker because it's just hand-rolled class instantiation. Or to put it another way: If your code is an example of "Poor Man's DI," then wouldn't any DI that wasn't handled through an IoC container? You are just creating objects with code -- nothing special. (or wrong, either)

If that's what "Poor Man's DI" means, there should probably be a new phrase to identify the practice that I've seen the phrase tied to -- as it's a "special" and unique practice. (Take that however you will. :) )
2012-11-07 01:02 UTC
Darren, I'll refer you to this answer for further details on the terminology choice. You do have a point, but I'm sticking to the terminology from my book.
2012-11-07 08:17 UTC
I guess people can call things whatever they want. I've seen & heard many references to the two-constructor pattern as Poor Man's DI and for a long time, but this is the first time I've seen the phrase used in your way. It's also the basic first time I've seen basic class instantiation given a special name.

Now that I think about it,the concept of "Poor Man's DI" and "Bastard Injection" seem to refer to different things. Given your definition, Poor Man's DI basically seems to mean that I don't use an IoC container. It's a concept defining the method in which the object is created. But Bastard Injection refers to what I think would be the more common use of "Poor Man's DI," the practice of creating two constructors. It's a concept defining the method in which the class itself is written. I guess, then, it's possible for me to use Bastard Injection with Poor Man's DI, so long as I don't call the default constructor?

As one more side note: I really don't like the name "Bastard Injection" due to the coarse language. I know it's an anti-pattern, but "bastard" is a word I'd never ever accept from myself or other developers in a professional setting, especially with a client. I just asked my wife, an public elementary school teacher and librarian, and she said that word would not be accepted in her class or at any school she's been at. I don't think it's helpful to give PG13-level words to programming concepts. :)
2012-11-07 13:04 UTC
Another thoughtful article; thank you!

How do considerations of lifetime management factor in? I may want Singleton here, Transient there, etc. That would seem to favor Explicit Register.

There's also the option of integration-testing the Composition Root to provide some type-checking.
2012-11-09 18:51 UTC
Bill, thanks for your comment.

When it comes to lifetime management, there are answers on more than one level.

On the pragmatic level, I've often found that in reality, most of my graphs tend to need to be Transient (or Per Graph) because some commonly used leaf node must be Transient (or Per Graph) for whatever reason. Once that happens, if most (say: more than 75%) of all objects are already Transient, does it really matter if a few more are also Transient? Yes, it could have been more efficient, but if you profile your app, you're most likely to discover that your bottleneck is somewhere else entirely.

On a more explicit level, it would be possible to define a convention that picks up a hint about the desired lifetime from the type itself. You could for example call a service "ThreadSafeFoo" to hint that Singleton would be appropriate - or perhaps you could adorn it with a [ThreadSafe] attribute...

Testing the container itself I don't find particularly useful, but a set of smoke tests of the entire app can be helpful.
2012-11-11 18:03 UTC


Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Tuesday, 06 November 2012 11:42:06 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!