In my previous post Building Windows Azure Services Without Compromising Testability I demonstrated how the Unity Application Block can be used to build a testable Windows Azure Service. Since then I started to play with the ServiceLocator in order to introduce a new testable code base in a brownfield environment.
As many of colleagues would agree, the ServiceLocator is often considered to be an anti-pattern, but there are some scenarios where it shines. Introducing an Inversion of Control (IoC) container in a brownfield project is a scenario where the ServiceLocator can be leveraged in order to reduce risks.
When we start working on a brownfield project, the outcome of our modifications is usually quite unpredictable. Rewriting the entire code base is costly and very risky. The lack of documentation makes it hard for a team to rewrite modules without forgetting features and accounting for undocumented behavior. The ServiceLocator can be used to introduce the IoC container without significantly altering the structure of the application. We can start introducing the new code base in very targeted areas. Consequently, allowing us to bring the application under test. Furthermore it allows us to refactor and reorganize the architecture of the application from within. As the project evolves its important to keep in mind that as we overhaul the brownfield project, the ultimate goal is to factor out the ServiceLocator in order to keep the IoC Container confined to the application’s bootstrap. This of course is not something that can be achieved instantly, it takes time and planning.
In a post titled Say no to ServiceLocator the author enumerates 5 rules that I feel are crucial to the success of projects that employ IoC containers.
1) Only store services in the IoC container. Do not store any entities.
I completely agree with this first rule. Entities should be instantiated in code and should bubble throughout the layers of your application through interfaces. The UI should ultimately bind to interfaces instead of concrete objects.
2) Any class having more than 3 dependencies should be questioned for SRP violation
I briefly eluded to this in a previous post where I asked “When is Dependency Injection too Much?”. Having too many dependencies usually result in code that is hard to maintain. The Single Responsibility Principle (SRP) is part of the SOLID Principles. These aren’t the absolute truth but they should be used as guide to help you design for maintainability.
3) Every dependency of the class has to be presented in a transparent manner in a class constructor.
IoC containers usually allow us to inject properties and methods. I strongly recommend not using these unless its absolutely necessary! By injecting dependencies through the constructor, it becomes easy to spot classes that have too much responsibility. Furthermore, it allows you to easily validate that you have not forgotten to configure your dependencies. Trying to resolve an object that has missing configurations will throw exceptions.
Injecting dependencies through the constructor does not require a dependency on your IoC framework. This is actually quite important, because this provides you with flexibility to change your IoC container in the future without changing code in your component.
4) Every constructor of a class being resolved should not have any implementation other than accepting a set of its own dependencies.
This rule goes along the same lines are rule 3 by reducing the number of ways that an object can be instantiated, we can reduce accidental complexity to a maximum. In other words, you should have a single constructor accepting dependencies as parameters.
5) IoC container should be explicitly used only in Bootstrapper. Any other “IoC enabled” code (including the unit tests) should be completely agnostic about the existence of IoC container.
This last rule is crucial, projects that do not follow this rule usually end up looking like a big ball of mud with an illusion of a feeling that the code is more decoupled that it really is…
Example
This example is based on the scenario presented in Building Windows Azure Services Without Compromising Testability.
The following BootStrap class uses the Unity Application Block to define the dependencies required to build the MessageProcessor object. The configured UnityContainer is then used to configure the ServiceLocator, which can then be used to resolve instances.
public class BootStrap { public void Configure() { var uc = new UnityContainer(); uc.RegisterType<ILogger, TableStorageLogger>(); uc.RegisterType<IMessageSource, QueueMessageSource>(); uc.RegisterType<IMessageDecoder, QueueMessageDecoder>(); uc.RegisterType<IMessageHandler, CommandHandler>("CommandHandler"); uc.RegisterType<IEnumerable<IMessageHandler>, IMessageHandler[]>(); var lifetimeManager = new ContainerControlledLifetimeManager(); uc.RegisterType<IMessageProcessor, MessageProcessor>(lifetimeManager); var unityServiceLocator = new UnityServiceLocator(uc); ServiceLocator.SetLocatorProvider(() => unityServiceLocator); } }
Then we using the ServiceLocator to locate a MessageProcessor instance in a Worker Role
public class WorkerRole : RoleEntryPoint { public override void Run() { // This is a sample worker implementation. Replace with your logic. Trace.WriteLine("Worker entry point called", "Information"); var bootStrap = new BootStrap(); bootStrap.Configure(); var processor = ServiceLocator.Current.GetInstance<IMessageProcessor>(); while (true) { processor.Process(); Thread.Sleep(10000); Trace.WriteLine("Working", "Information"); } } public override bool OnStart() { // Set the maximum number of concurrent connections ServicePointManager.DefaultConnectionLimit = 1000; // For information on handling configuration changes // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357. return base.OnStart(); } }