A Dependency Injection example with Spring.NET
As requested, here is a real world example of how I used dependency injection to simplify a project, increase modularity, and subsequently increase testability.
Here’s the project. I have a successful website called SimpleTracking.com which allows you to track packages using a simple, common user interface. It also allows you to track pages using RSS.
Here is a list of features:
- Supports multiple shippers, including FedEx, DHL, and USPS. The tracking number is resolved to one of them, and if a tracking number could belong to more than one, they are called simultaneously, and the one that returns the results is used.
- Results are cached to avoid overusing the shippers servers
- Errors are handled appropriately
I boiled the design into a tree of classes:
To greatly simply the design, I decided that each module would implement a common interface. After all, they all take in a tracking number, and return tracking data. Here is the ITracker interface:
public interface ITracker
{
TrackingData GetTrackingData(string trackingNumber);
}
Simple enough? Every class in the diagram above implements the same interface. If I want to add additional functionality, such as logging for example, I can simply add a class to the chain, and implement the same interface.
Now I can wire it up with Spring.NET:
<object name="postUtility" type="YTech.ShipperInterface.Tracking.Http.PostUtility, YTech.ShipperInterface" /> <!-- The trackers that actually do the work --> <object name="uspsTracker" type="YTech.ShipperInterface.Usps.Tracking.UspsTracker, YTech.ShipperInterface"> <constructor-arg ref="postUtility" /> <!-- Code removed for readability... --> </object> <object name="fedexTracker" type="YTech.ShipperInterface.FedEx.Tracking.FedexTracker, YTech.ShipperInterface"> <constructor-arg ref="postUtility" /> <!-- Code removed for readability... --> </object> <object name="dhlTracker" type="YTech.ShipperInterface.Dhl.Tracking.DhlScreenScrapeTracker, YTech.ShipperInterface"> <constructor-arg ref="postUtility" /> </object> <object name="simulationTracker" type="YTech.ShipperInterface.Tracking.Simulation.SimulationTracker, YTech.ShipperInterface" /> <!-- Combine all of the other trackers into one stream --> <object name="multiTracker" type="YTech.ShipperInterface.Tracking.MultiTracker, YTech.ShipperInterface"> <constructor-arg> <list element-type="YTech.ShipperInterface.Tracking.ITracker, YTech.ShipperInterface"> <ref object="simulationTracker" /> <!-- Order these by popularity --> <ref object="fedexTracker" /> <ref object="uspsTracker" /> <ref object="dhlTracker" /> </list> </constructor-arg> </object> <!-- Cache the upstream tracking data --> <object name="cacheTracker" type="YTech.ShipperInterface.Tracking.CacheTracker, YTech.ShipperInterface"> <constructor-arg ref="multiTracker" /> </object> <!-- Handle errors by logging them, and returning a special ErrorTrackingData object --> <object name="MainTracker" type="YTech.ShipperInterface.Tracking.ErrorHandlerTracker, YTech.ShipperInterface"> <constructor-arg ref="cacheTracker" /> </object>
Now in my code, this is all I have to do:
var ctx = ContextRegistry.GetContext();
var tracker = (ITracker)ctx.GetObject("MainTracker");
var td = tracker.GetTrackingData("my tracking number");
Every piece I’ve written is fully testable. I even created a class that posts data to a remote web server, and returns the response. This allows me to completely test the tracker classes. They don’t care if they’re hitting against a real server, or an in memory request/response mock class.
I have nearly 100% test coverage, and making changes to the site is a breeze.
The next step is to convert the actual web project to an MVC project so that I can unit test the actual page functionality.
Hopefully I’ve given a good example of how inversion of control can be a really good thing. Have any more questions about the architecture? Feel free to leave a comment.
Note: I haven’t replaced the code on the live SimpleTracking.com site yet, but I plan on upgrading in the next couple of weeks.
