Inversion of Control containers can be a very powerful tool for decoupling spaghetti code in large software systems. However, with any power tool, you can hurt yourself badly if you don’t use it correctly. In this post, I present “10 commandments” to help you avoid causing problems with IoC, with a particular focus on very large software systems with many developers and hundreds of interfaces. I am currently using Unity, but these tips apply to pretty much any IoC container.
1. Configure everything before your first resolve
It is possible to ask the IoC container to resolve an interface before you have finished configuring the container. So long as it knows how to make the interface you asked for and its dependencies, it will have no problem fulfilling your request. But if your application hasn’t yet finished configuring the container yet, you run the risk of getting the wrong thing. Consider the following simple example:
IUnityContainer container = new UnityContainer(); container.RegisterType<IFoo, Foo>(new ContainerControlledLifetimeManager()); var f1 = container.Resolve<IFoo>();
Fairly straightforward, we ask for IFoo and get an instance of Foo. But what if we hadn’t quite finished configuring the container, and some other part in your app attempts to override the registration for IFoo:
container.RegisterType<IFoo, Foo2>(new ContainerControlledLifetimeManager());
Now, whenever we attempt to resolve IFoo, we get Foo2. But the initial component that did an early resolve is using the wrong implementation. This can make for horrible debugging sessions. Configure your container completely, before you start to resolve things from it. Which leads us to our second commandment…
2. Don’t pass the container around
What I mean here, is don’t pass around the top-level interface that allows further configuration of the container. In Unity, this is the IUnityContainer interface. It might feel very powerful to send it around, since it allows other parts of your application register new rules, but it opens the door for the kind of bugs we just discussed.
But what about just a Service Locator interface. Can I pass one of those around? Onto commandment 3…
3. Avoid passing an IServiceLocator around
Passing a service locator in as a dependency gives your class great power. It can ask for anything it wants, which is super convenient. But it also introduces some problems.
First, it means your class no longer advertises what it needs in the constructor. Without examining the code you can’t be sure what needs to be in the container for the class to work correctly. It means that unit tests, or callers of your class that aren’t using a container, will have to mock a container just to instantiate your class.
This has been called the “Hollywood principle” – Don’t call the DI Container, it’ll call you. Just put the interfaces you really need in your constructor.
4. Avoid making the container a singleton
Making your container a singleton is the quickest route to providing access to all your services everywhere in your application. It can seem like a great idea because wherever you are, you can just do this to get whatever interface you like:
var foo = MyContainer.Instance.Resolve<IFoo>();
But there are two big problems. First, this introduces hidden dependencies into your class. Instead of your constructor advertising its dependencies, we again must examine the code to know what needs to be set up in the container.
Second, it assumes that all parts of your application will work with the same container, and the same implementations of each interface. This may be true for small applications, but in large enterprise systems, there may well be the need for multiple IoC containers. In our systems, we make use of Unity child containers, allowing different sections of the application get access to their own implementations of interfaces. With a singleton container, this is impossible.
5. Avoid constructor bloat
One of the great things about IoC containers is that you don’t have to call constructors yourself – the container does the hard work for you. This means you can have a dozen constructor parameters, each representing a different dependency, and yet without ever having to write code that calls it.
public MyClass(IFoo foo, IFoo2 foo2, ILog log, IExporter exporter, IEmailer emailer, ISettings settings, IAudit audit) { }
That is, until you want to test it. Then you have to mock up all of those interfaces. And probably you will find that your class only needs to call one or two methods on each. This is the time to apply the Interface Segregation Principle and replace them with one or two more focussed interfaces that represent the real dependencies of your class under test.
6. Avoid property injection
Most IoC containers offer a way to let you put attributes on properties in order to tell the container that it needs to set that property after constructing the object. In Unity you use the Dependency attribute:
class Bar { [Dependency] public IFoo Foo { get; set; } public Bar() { } }
Although this seems like a great feature, it has the effect of hiding this dependency from anyone who is constructing your object without an IoC container. At the very least, your class should report a good error message if someone forgets to set up a property dependency.
7. Document your interfaces
If you are using an IoC container, chances are you are working on a large system, and other developers are resolving interfaces that you put in the container.
Often developers will add good comments to the concrete implementation of a class, but spend very little time commenting the interface (who likes to write the same comments twice?). So in the concrete class we might have a comment like this:
/// <summary> /// Call this to process all the files in the InputFiles collection, using the rules from the Rules collection /// </summary> /// <param name="mode">Processing mode, 0 = replace, 1 = update, 2 = overwrite, 3 = test only</param> public void Process(int mode)
but in the interface, we couldn’t be bothered to type that all again (and we hate cutting and pasting anyway) so we just have this:
/// <summary> /// Process /// </summary> void Process(int mode);
But it is the interface that is the public API for your service. The comments on the interface will be used to display Intellisense to the user. The caller may not even have access to the code for the concrete implementation. If you are going to spend time writing good comments, write them on the interface. Here’s the difference in intellisense experience:
versus:
8. Don’t depend on Dispose in a specific order
When you Dispose your IoC container, it will go through all of the IDisposable instances it knows about and call Dispose on them. But this can introduce a problem, because we cannot guarantee the order the services are disposed in. If you make a call into another service in your Dispose method, how do you know that service hasn’t already been Disposed?
Instead, design your services in such a way that they can be disposed in any order, and use events or some other form of messaging to report to your system that a shutdown is about to happen, allowing any last minute logging, saving etc to be done beforehand, while all the services are still up and running.
9. Make sure Lifetime Management is communicated
If you call Resolve<IFoo> twice on your IoC container, you might get two new instances of the Foo class, or you might get the same one twice. Without looking at how your container is configured, you have no way of knowing. But this can be a real headache if IFoo implements IDisposable. How do you know whether you ought to call Dispose on it or not?
I don’t know of any slick solution to this, but I would typically avoid instances where a Resolve gives you something you need to Dispose yourself. Instead I would return a factory object that makes it very clear you are building a new instance that you are in control of its lifetime yourself. Whatever approach you use, make sure your whole development team understands it. You don’t want someone Disposing a service too early, resulting in the next person to use it getting a nasty exception.
10. Document your public API
In a large system, a container can easily fill up with a lot of interfaces. The trouble is, not all of these are at the same level. Some are high-level interfaces, allowed to be called from anywhere, whilst other things are only in the container so they can fulfil the dependencies of those high-level interfaces. It means that you run the risk of developers guessing incorrectly about which interface they are supposed to call to achieve a particular task. They will assume that if they can get at it from the container, then they must be allowed to call it.
Now there are ways of having interfaces defined in your container that people can’t get at from the wrong place by making good use of assemblies and the internal keyword, but really, you need to make it easy for developers to know what is in the container and how they are intended to use it. This may well involve maintaining an API document, and also means writing good comments on the interface. If you don’t do this, don’t be surprised to see code that inadvertently circumvents key functionality by calling into a component at too low a level, or the wheel being reinvented, simply because a developer didn’t know the container included a service that had the desired behaviour.
Do you have any tips for getting the best out of IoC containers? Please let me know in the comments.
20 comments:
Concerning 5. (Constructor Bloat), another option is to pass in a Verbs instead of dependencies, as illustrated here: http://thorstenlorenz.wordpress.com/2011/07/23/dependency-injection-is-dead-long-live-verbs/
This way you only have to mock out the methods themselves instead of the whole dependency. It would also possible to pass all the verbs in as properties of a Config class. That way the constructor is simplified and it is easy to see what the class actually uses (e.g. exactly what methods).
Good list, with the exception of #7. That "documentation" adds nothing but noise, with the exception of describing the use of an int which should be a self-documenting enum. Remove that and the doc just restates the presumed classname and its properties.
@Thorsten - thanks for the link, it is an interesting article, and have experimented a bit in the past with just passing functions in rather than interfaces as dependencies. However, it doesn't work great with an IoC container.
@blorq - I am a big fan of the idea of self-documenting code, and agree that many comments would be unneccessary if we named functions better. However, that one was aimed at very large systems with dozens of developers and hundreds of interfaces where you do need ways of finding out how you are intended to use a component. Often you have no power to change the way an interface has been defined due to dependencies outside your control, but you do need the caller to understand how your method should be called. Comments on the interface (instead of on the concrete class where developers usually put them) are very helpful in this scenario.
Rules 5 and 6 conflict. You either use contructor or property injection. Rules usually are that required dependancies are passed via the constructor.
Rule #8: When you did not use 'new' then don't call dispose and never have an interface inherit from IDisposable. Problem is that not all code bases follow these simple rules.
Having too many constructor dependancies probably violates seperation of concern and indicates a design error. Just refactor those classes and split that god class in several classes.
By the way, I have seen lots of code that only use interface dependancies but when classes are internal you can just as well use class dependancies. This works very well when you have lots of command classes.
@Ramon - I'm not sure why you think rules 5 & 6 conflict - I'm not advocating using both.
Your thoughts on rule 8 are interesting. There would need to be a way to dispose long-running services that the container has created. Do you have a pattern that you follow on this?
This is a good list. It's always good to see other people arrive at basically the same patterns as the ones I describe in my book, as this reinforces that they are really patterns - i.e. solutions that lots of people have arrived at independently of each other.
The only item on the list where I disagree is #9. Both exposing IDisposable on an interface and/or injecting a factory with the sole purpose of managing lifetime are signs of leaky abstractions.
A much better way to deal with the decommissioning concern is to employ the Register Resolve Release pattern.
However, Unity's Release functionality is broken, so RRR doesn't work out of the box with it. Still, it's possible to tweak Unity to make it work, but that's another story ;)
@ploeh, thanks for the feedback. I'll look into the RRR pattern - sounds like it could be useful.
#6. Another negative side effect of property injection is temporal coupling. You have to create the instance and inject the property before using the method that depends on it.
It's not clear from the outside that this class has nuances that have to happen just right for things to work properly.
I'd like to introduce an 11th commandment ;)
#11. The IoC container shouldn't dictate the code base.
The IoC container is used to compose the application but it shouldn't infect the code base by dictating how classes are defined.
#3 (Service Locators) might be good for web. There you setup your IoC for every request and can (theoretically) predict the whole flow.
In desktop applications you typically setup the IoC at app start which means you need something in the places that correspond to requests (button clicks). Here I break your rule #3.
@Torbjörn, there is no requirement that you need access to the service locator to achieve that. There are plenty of approaches allowing your button click handlers to action a command without them needing root level access to everything in the container.
@Mark H: The button click handler shouldn't have direct access to the container (rule #2). But it needs access to something that can explicitly resolve a type (where that type must implement a certain interface). I call that something a Service Locator.
@Torbjörn why can't you inject the necessary interface into the the form that contains the button?
@Mark H: We might be talking past each other here...
When I click the button I would like to create a new instance of a view. I want the instantiation to happen when I click the button, not at application start up.
That is, somewhere in the button click handler I need to call resolve. I don't want the button to have direct access to the container, but want to restrict what it can resolved. The class that handles this restriction (and that has access to the container) is what I call Service Locator.
@Torbjörn, OK I see. I wouldn't call that a ServiceLocator. A service locator has a Resolve() or Resolve(Type t) method, allowing you to get any type from the container. Sounds like you have a simple Func that returns a given type.
@Mark H: So your Service Locator is a read-only abstraction of the container (which isn't really a Service Locator either :) Now I agree with your rule #3!
But what is your opinion about my example (where what I called Service Locator is actually more of a View Factory)? Is it ok to have such class?
The reason I ask is that I've heard some people argue that it's not ok...
@Torbjörn, one of the main goals of dependency injection is loose coupling. Does your view factory end up tightly coupling you to a specific view implementation? Can you change what view you actually get from the outside? Can you unit test without the real view getting created? Assuming those issues are dealt with in some way, I don't see a problem with injecting a view factory.
@Mark H: Cool, then we agree :)
Post a Comment