(This is a continuation of a previous post)
In the previous post, we adapted our existing code to allow the use of an IoC Container, and prepared the container so it is ready to use. In this post, we put that foundation to work and start altering our logic.
Install Service Locator
When coding new applications, Inversion of Control (IoC) is best done using Dependency Injection, or DI. However, DI assumes all necessary classes are registered with the container and available for resolution. When converting an existing application, it is not practical to convert all necessary classes to DI in one step. Trying to do so can dramatically increase the scope of the work, making the conversion project more difficult and possibly extending it beyond a single sprint.
In order to avoid the requirement of converting the entire application at once, we introduce an intermediate step of using a Service Locator. Using the Service Locator pattern allows you to convert objects to the IoC pattern slowly over time instead of requiring a massive conversion effort. Once an application is fully converted to Service Locator, it is a trivial step to further convert it to Dependency Injection.
The Service Locator is a static class that is referenced from all parts of the system (this is the part that Service Locator detractors object to). As such, it needs to be located in a central project that can be referenced by all classes that need it. Depending on how your IoC container works, this project may also need to be able to reference the projects where the Interfaces and Classes are kept. If you have a large solution, finding a good place to put the Service Locator can be one of the most difficult parts of this conversion effort.
The contents of the Service Locator are quite simple. It’s basically just a class factory that uses the IoC container to resolve classes. Here’s a basic example.
In this example, we are creating and configuring our container all within code. Depending on the container you choose, configuration can be done in different ways. The key thing to accomplish is for the Service Locator to have a reference to the IoC Container so it can resolve requested classes.
If you are working with an ASP.NET project (as opposed to a Windows app), you may be temped to use the Application object as your Service Locator. By registering the container in the Application object, you can resolve the container anyplace in your code and use it directly. This is a convenient and quick way to solve the problem but will cause some problems down the road. It will force a reference to the particular container throughout your code, locking you into the IoC container you’ve chosen. By only accessing the container via the Service Locator class, it avoids this reference and allows you to modify the chosen IoC container, as well as providing an extension point for adding custom resolution logic, where necessary.
With our IoC Container now available via the Service Locator we can begin adapting our code to use it.
Convert Class Creation to Use Service Locator
Once the Service Locator is installed, you will be able to sweep through the application and find all code that creates new objects, replacing those lines with calls to the Service Locator instead. For example, the old code could look like this:
With our Service Locator, this same call would become this. Notice the only change is in the line creating our repository. All other logic remains exactly the same, reducing QA requirements.
Once you’re done with this, you may (hopefully) remove all references to the namespace containing the concrete class. I say “hopefully” because there may be other classes in that namespace that you still have hard references to.
Convert to Dependency Injection (Optional)
By replacing the “new” statements with calls to the Service Locator, you are done with the conversion to IoC via Service Location. The application is now more loosely coupled than it was before, and much more testable. However, many people dislike the Service Locator pattern, saying that it just moves the dependency to a different class. If you agree with this, there is an easy way to continue with the conversion and move to Dependency Injection.
Start by finding a class that has no references to other classes. In large applications this can be difficult, but it is worth the effort because this is the first domino that must fall.
Find all places where this class is referenced and alter the constructor of the calling code to have that class injected instead. For example, here’s a sample of a class that does not reference any other classes.
Here is a sample calling class.
We add a constructor to the CallingClass that allows the dependency to be passed in from the outside, like this.
In this section, notice we have removed the ServiceLocator call, and are now using a class-level variable that is injected in during object creation. Assuming all code that creates a CallingClass object is using the ServiceLocator, this class is now converted to Dependency Injection and references to the ServiceLocator can be removed.
This process can be continued either ad-hoc as time permits, or as a concerted effort by a dedicated resource. Each of these small conversion can stand by itself, and so they can be done over multiple sprints while the application is always releasable.