John Gobble 2
Now that we've discussed dependency injection basics and how dependencies are contained (connected together), I'd like to walk you through some final DI concepts and highlight the DI acid test: can your units of work be tested in isolation. Lastly, I've included some links to open source, IoC containers, that are available for your dependency injection pleasure as well as to link to open source mocking frameworks.
The DI Acid Test
Unless you've practiced TDD, you should now be able to demonstrate that all concrete dependencies have been factored out of your code by writing unit tests for each testable component within your system. I personally like to draw testing boundary lines around my layers (or tiers). For example, if I am using the Manager Patter, then I draw a circle around just one Manager (e.g., the IBlogManager), and unit test each public-facing capability represented by that manager (e.g., my GetBlogEntries method). If I've completely factored out my dependencies (e.g., my IBlogDao), then I should be able to create mock objects for each of my interface dependencies and fully test my Manager's functions in isolation.
As a quick reference, here are some popular mocking frameworks.
Once you've validated that your design and implementation are DI-centric (every dependency is injectable), you are ready to wire up your Interfaces to the concrete classes that you want the IoC container of your choice to instantiate at runtime when a given Interface dependency (constructor or property) is encountered.
When reading yourself for an IoC implementation, here are a few questions and rules of thumb that you should consider before making your selection:
- Code Bindings - Does your IoC candidate support wiring up bindings through code? This feature is useful when you need compile-time checking to validate that your Interface-to-Concrete mapping is valid (e.g, your class implements the interface).
- Config / XML Bindings - Does your IoC candidate support wiring up bindings through a configuration file? This is useful when you'd like to change your concrete binding frequently or don't want to recompile to absorb the binding change.
- Scoping (list complements of Ninject):
- Transient - A new instance of the type will be created each time one is requested. This is the default scope if none is specified.
- Singleton - Only a single instance of the type will be created, and the same instance will be returned for each subsequent request.
- Thread - One instance of the type will be created per thread.
- Request - One instance of the type will be created for each Web Request.
- Extensions: Does the IoC have extensions for commonly used features/patters such as Interception, Logging (e.g., log4j, log4net, etc.), Factories, etc.? If not, then you may find yourself having to switch downstream during development or support phases.
- De Facto Standard & Support: Has the community embraced this container so that it will be supported over the next 3-5 years? If not, then you will find yourself fixing bugs or having to add your own features.
Here's a short IoC reference list of the most popular containers at the time of this writing.
My Personal IoC Container Favorite
I've spent most of my professional, so-called career in the Microsoft/.NET technology space. In my opinion, the two IoC container industry leaders in .NET are Unity and Ninject. While I have nothing against Unity, Ninject has become my go-to player due to its stability, wide use in the community, and its plethora of extensions, including those for MVC, WCF, Logging, Factories, and event brokers, to name a few.
To conclude this mini-series on Dependency Injection, let's review the following concepts:
- Design by Interface: Ensure that components in your system or application design depend on Interfaces rather than concrete versions of classes.
- Refactor, Refactor, Refactor: If your components rely on concretes classes, factor those concrete dependencies and replace them with Interface versions.
- Validate Your Design: Unit test each component of your system or application in isolation. If this can be done by injecting mockup versions of your interface dependencies, then your design will support IoC and IoC containers.
- Implement: Choose an IoC container that fits the bill for your system or application needs. Sometimes simpler is better, but don't sell yourself short by choosing a feature-poor container.