In this post, we will discuss the process we use to refactor an existing application to use Inversion of Control. The main goal of this process (aside from the obvious) is to keep the application in a release state throughout the process. As long as they are done in order, these steps can be done in any sprint and the application may be released at sprint end.
For this post to make sense, there were some assumptions we had to make while writing it.
Assumption 1: .NET
The first assumption is that the target application is written in ASP.NET. Unfortunately, there is no IoC Container that exists for Classic ASP, so converting an existing ASP application is not possible. This article DOES apply for hybrid Classic/ASP.NET projects, but only for the .NET side.
Assumption 2: Continuous Deployment
Alobria is an Agile development shop, and we practice continuous deployment and continuous integration. The process for introducing IoC embraces these practices by breaking the process up into small, stand-alone steps. Each of the steps below may be done individually and in a single sprint without making the application unreleasable. During sprint planning, you can pick up one or more of these tasks along with any other backlog items, and still release at the end of the sprint.
We developed the process like this because the majority of our work is with existing systems that cannot be taken down for maintenance. The steps were designed to never get in the way of the release schedule.
Though Inversion of Control is a software architectural pattern that has been around for a while, there is a significant number of legacy applications that do not use it, but would benefit from the loose coupling it provides. The most common implementation of IoC, Dependency Injection, is difficult to implement in existing applications. A basic assumption of IoC Containers is that all necessary objects can be resolved using the container. This means that legacy applications must be converted all-or-nothing. Each and every class and service must be converted to DI in one release for it to work.
To see why this is so, consider the following dependency graph.
In this graph we can see that the classes are tightly coupled. In other words, there are hard references from one class to another. If we were to try to convert this application to Dependency Injection, we might start with the Controller. However, that requires references to the DAL, EmailService, APIServices, and ErrorLogger so the IoC Container must be able to resolve each of these for the controller. To be resolved, each class that these classes depend upon must also be resolvable, etc. As the dominoes fall, we see that we must convert all classes at once or this approach will not work.
However, there is a solution. This article will lead step by step through the process of converting an existing application to use Dependency Injection, without the need for rewriting the application or taking it down for an extended period of time.
One of the core reasons we convert applications to IoC is for loose coupling. This is achieved by updating the calling code to use an interface instead of public methods on the object itself. (There are many discussions of when, why, and how Interfaces should be used – check here, here, and here.) This requires that all objects we want to store in the container be associated with an interface. Luckily, converting an object to an interface is a trivial prospect.
Interfaces can be introduced without changing the logic of any of your code. It is simply a slight change in references in the code. Logically, the execution is exactly the same, which minimizes the QA requirements.
First decide what namespace you will put the interface in. You can put the interface in the same project as the logic, but i prefer to have it in a different namespace (project). I normally have a project called Infrastructure where I keep generic logic that has no knowledge of the specific application, such as string manipulation utilities, an email sending service, etc. If you have a similar project, this is a good location for the interface, or you can create a project specifically for holding interfaces.
When creating the interface itself and attaching it to the class, there are many automated tools that can help. If you don’t happen to have one handy, you can easily create an interface by hand by simply exposing all the public methods and properties in the concrete class. For example, consider the following class.
Implementing an interface for this class is a matter of simply copying the signature of each public method or property. Here is the interface for this class.
Once this is done, scan through the code and ensure that any references to the concrete class are changed to be references to the interface. The only exceptions are the locations where the class is created – these must remain references to the class. For example, this is how our Example class would have been used before introducing the interface.
This is how the calling code would look after converting to the interface.
Add IoC container
Once the interfaces are added to your project, you must add the IoC container. There are many containers available for download, and all have their pros and cons. Which one you pick is up to you and will not affect how you proceed with your conversion.
The container should be initialized once at application startup. It needs to be configured to provide the proper services in response to requests made by the application. In other words, it needs to be setup so when your application asks it to resolve one of the newly created interfaces, it knows which concrete class to actually create. Many of the IoC containers available have an option that scans a given DLL or namespace and automatically maps the given interfaces. This is an excellent way to go and can save a lot of configuration time as you add classes to your container.
Once the container is installed, remember to add unit tests to ensure that each class can be resolved as necessary.
A Note for Agile Shops
Something to note is that we are only introducing the IoC container, we are not converting any code to use it yet. After this step is complete, you will have a fully configured container that is never used (except in your unit tests). So far, we have not changed any logic anywhere in the application so everything should continue to work exactly as it did before. I point this out because if you are an Agile shop with short sprints (many of our clients have 1 week sprints), this is a good place to stop for the sprint. Everything on this page is easily done within the confines of a single sprint, and you can still take on other tasks that add features or fix defects.
In the next post, we will discuss how to use this foundation to complete the installation of an IoC container and Dependency Injection.
(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.