Dependency Reversi

Tuesday, January 19, 2010 9:19 PM

I have to admit, when I read Uncle Bob’s post about dependency injection, I really didn’t expect the response it would get since it struck me as being relatively uncontroversial.  So Davy’s reaction rather surprised me.  Davy had two major objections to the original article:

  • The contention that IoC containers aren’t always appropriate.
  • The advocating of the use of abstract factories.

Now, when I teach constructor injection to developers around my company, I always teach it in the manner Uncle Bob describes: just pass things into the constructor.  Sooner or later someone asks where things come from initially, and I explain that it all gets set up in the main method.  Let’s revisit why we’ve done this: we’ve separated stable code from unstable code since we’ve moved the wiring into the main method.  IoC containers are all about configuration.  If your configuration isn’t complex, I don’t particularly want to confuse a developer by introducing the concept.  I usually let them hit the point at which wiring has become painful and then show them the next step.

Don’t get me wrong, I love IoC containers.  They’re power tools and I wouldn’t do without them on complex projects.  But not everything I write is that complex, and I wouldn’t use an IoC container for Solution Transform any more than I would use an electric drill on an Ikea table; the likelihood of woodchips flying everywhere is just way too high.

Charlie and the Abstract Factory

A point that I don’t think is made enough is that the SOLID principles aren’t just about what, they’re about where.  Dependency Inversion, however you practice it, moves your externalities into one place: the Main method.  However, Uncle Bob’s second point is about location as well.  Now, I don’t think many people in the .NET world are still using attribute-based registration but the fact remains that it does cause your entire code to take a dependency on your container.  If you and every possible client you can envisage uses that container that’s not the end of the world.  The same argument applies to any invariant dependency.  For anyone else, it’s at best rude and quite likely to lead them back into the kind of configuration hell IoC is meant to avoid.*

As I understand it, the main objections to the abstract factory concept are as follows:

  • They will result in a lot of abstract factories.
  • The factories will be dependent upon the container.

I think the first problem is overstated.  As Davy says himself, the constructor injection behaviour of the container ensures that most resolution is handled seamlessly.  When would you actually need to use container resolution?**

  • In the main method. 
  • Where you need to create multiple objects of the same type.
  • Where you need to dynamically create different implementations for the same interface depending on your current state.

The first isn’t a problem, you’ve already got a hard dependency on your container there.  It’s the other two where you need an Abstract Factory.  Here, you’ve got three choices again:

  • Write a dedicated implementation that doesn’t use a container at all.  This is the classical (GoF) understanding of how to implement the pattern.
  • Use your container’s resolution API
  • Hide your container behind an Adapter pattern.

I’d argue the first approach can be appropriate, but may be a code-smell in code that uses an IoC container.  It indicates you’re not really happy with the resolution behaviour of your container.  On the other hand, nServiceBus takes the last approach with the IBuilder interface and implementations.  Davy’s own Agatha does something similar.  However, these are slightly misleading examples since they’re still phenomenally general.  If you’re throwing an interface that general around your code, you’re either writing a framework or violating the Interface Segregation Principle. 

You are in a Maze of Twisty Principles, All Alike

Imagine if whilst reading the Gang of Four book, you were told that the correct maze factory implementation was this:

public interface IGeneralFactory
{
    TResult Make<TResult>(IDictionary<string, object> parameters);
}

Rather than this:

public interface IMazeFactory {
    IMaze MakeMaze();
    IRoom MakeRoom(int id);
    IWall MakeWall();
    IDoor MakeDoor(IRoom room1, IRoom room2);
}

The latter code communicates intent and limits the interface to the operations the client is going to need.  The former is overly general and its purpose could be anything.  It’s so general it would be pretty impossible to simulate, which quickly leads to the “containers in tests” anti-pattern.  Unless you believe that there’s something magical about inversion of control containers that make standard design concerns go away, you’re going to be favouring the latter interface in your code.  Moreover, by putting the maze factory interface with the library which consumes it and the implementation with the registration code (which is container specific) we’ve remove all of our dependencies in our library code on the framework.

Uncle Bob’s example isn’t really helped by the fact that it doesn’t scale.  Although perfect for mid-sized apps with relatively few requirements for dynamic resolution, in practice you’re more likely to implement the factories as taking a container as a dependency, with a similarly modular system for the registration of components.  (Krzysztof has already remarked that .NET containers seem to have better tooling in this regard, but the principles apply either way.)  However, the classes are still coupled, and should be located in the same assembly.**

Removing Tedium

This last part isn’t really about coding principles, but let’s take this further.  There’s an unspoken third objection to using abstract factories religiously: that the code is dull and repetitive.  It bulks up our registration code with form-filling noise.  Wouldn’t it be better if you could just inject the interface into your container and have it auto-generate the implementation class for you?  This would shorten your registration code, keep the complete lack of hard dependencies on your containers and generally make your code shorter and more flexible.  Well, that’s the point I reached a year ago with AutoGen.  Castle Windsor had a crippled implementation at that time, but it’s usable these days.

Seriously, would you rather:

  • Take a hard dependency on your IoC container throughout your code.
  • Write “Component.For<IMazeFactory>().AsFactory()”?

Not all containers expose this functionality (I’d argue that most should) but that’s not a problem.  You’ve still got the technique Uncle Bob describes to fall back upon.  If more did, we’d be a lot further along the road to container independence than we are now.

*I could expand on this point further, but I’m not convinced anyone is likely to contend with what I’m saying here.

**I’ve used the terms “resolve” and “assembly” throughout here, because I’m a .NET developer.  However, the argument doesn’t noticeably change if you say “getInstance” and “Package”.

UPDATE: I've tightened up the style at points to improve readability. Semantically it's unchanged.

UPDATE 2: Jeffrey Palermo made a comment to the original post which said "[We are] lying to ourselves [...] that it is not a global and that it in itself is not a dependency". He then followed it up with a post which ended up proposing a poor man's IoC as a global variable. As I've said before, I don't agree with approaches like this.

UPDATE 3: Oren posted a counter-example to the idea about manual constructor injection. He seemed to be arguing that just because you couldn't write expedia.com that way you shouldn't try to do so for smaller programs. In particular, two things about his example aren't shown on the diagram: the need for lifecycle management and the presence of more than one controller. A straight console program that required that set up would probably be better written just using manual injection.

UPDATE 4: Davy posted a response to this article that makes it clear that we're actually pretty much in agreement as to best practice (although he doesn't have the tooling that's the final step in the article). I now think that his principal objection to Uncle Bob's factory code is that it puts registration and resolution into the same class, which I agree doesn't scale. There's another case of "container as global variable" in his code, but I think I'll work that into a separate blog post. (Incidentally, one major objection to putting registration and resolution into the same object is that it violates SRP. However, that argument would also apply to the container itself.)

UPDATE 5: Uncle Bob followed this up with a similarly back-to-basics post about mocking frameworks. Again, his emphasis is on how the principles are independent of the tools, and that the tools are sometime overkill. However, I think I'm going to sit this one out. :)

Comments
No comments posted yet.
Something to add?

Talking sense? Talking rubbish? Something I'm missing? Let me know!

Fields denoted with a "*" are required.

 (will not be displayed)

 
Please add 1 and 1 and type the answer here:

Preview Your Comment