So Derick Bailey asked me
the other day a few weeks ago to describe how we use our IoC container. I wouldn’t call it “best practices”, though some of these tips came from the creator of our container of choice, StructureMap (Jeremy Miller). Although IoC containers are everywhere these days, you don’t see many instructions on where and how to use them. We’ve developed a consistent approach to using our favorite container of choice, StructureMap. The basic ideals behind our approach are:
- Keep the framework at a distance
- Focus on the dependency inversion principle
Hiding the framework
If you look through one of our projects that use StructureMap, you’ll be hard pressed to find any usages of StructureMap. In fact, one recent project had exactly one file that used StructureMap.
Just like any framework, littering your code with references and usages can make it nearly impossible to change to a different container. I’ve heard this quite a bit, that it’s nice to change the framework you use at any time. This has never actually happened with me, so I’m still dubious that anyone designing their application to be able to change frameworks at any time, actually has. However, the less we litter our code with the framework, the less impact the framework has on our code. Bending design to accommodate usage can spoil benefits of the framework.
Looking at StructureMap, hiding the framework means preferring the fluent configuration or the xml configuration. With fluent configuration, I get compile-time safety of all my types, and it plays well with refactoring tools like ReSharper.
Minimize calls to the container
Following the first rule, we shouldn’t have a lot of calls to our container to instantiate things. Ideally, we would like to reduce our interaction with the service locator (ObjectFactory in StructureMap) to one call to get an instance. We like to relegate the ObjectFactory calls to infrastructure-level code, such as the IInstanceProvider for WCF.
Reducing calls to the container from our code also greatly eases the burden in our tests. It’s possible, but just annoying, to configure the container just for a unit test because our class calls directly into the container. When the ObjectFactory call resides in some infrastructure piece, our entire domain layer can be completely free of any knowledge of the container. While nice from a swappability perspective, its true value lies in a higher cohesion and greater separation of concerns. If I don’t have to worry about StructureMap calls when I need to change a class, I can focus more on its core purpose.
Prefer constructor injection
Dependency injection comes in (basically) two flavors:
- Property injection
- Constructor injection
Property injection should be used only with optional dependencies. An optional dependency is one that the class will still function properly. Examples of optional dependencies are things like logging frameworks. Constructor injection should be used for required dependencies. A required dependency is one that the class will not function properly without it.
For users of a class, constructor injection through constructor arguments conveys far more meaning for required dependencies. If a dependency is required, then it should be required for instantiation. If you can’t use a class without a dependency, don’t let anyone use the class without it.
Prefer interfaces over abstract classes
This one is another in the “conveys more meaning” category. An interface is a contract that conveys a concise set of operations. I can do far more with interfaces than abstract classes, as interfaces support multiple inheritance, while classes do not. If some default behavior is needed, I’ll supply an additional abstract class that implements the interface.
Test your configuration
Just like anything else in our system, automated tests find bugs in our configuration much faster and more efficiently than manual testing. Automated tests are also far more reliable, of course.
Typically, we test any interesting configuration. This includes the top-most dependencies in our system (which will then load the entire object graph), as well as any custom configured dependencies. For array dependencies, we test that the class has the correct array with the items in the correct order. Before we tested our configuration, we would get burned with error messages after running the software. With automated tests in place, we have confidence that our container is configured correctly.
No calls to the container in a test
Unless you’re testing the container, the container should not show up in your unit test. If we’re following the first two rules laid out, we don’t have any work to do. If I need to configure the container for a unit or integration test, it’s a good sign that I’m doing something wrong. Configuring the container inside a test is possible, but it muddies the intent of the test. The container then becomes another opaque dependency, which defeats the purpose of using an inversion of control container.
Guidelines aren’t rules
Of course, there are always exceptions here. But I always take a second look at my code if I feel it’s necessary to not follow the above guidelines. It’s a smell that I’m probably doing something wrong.