Design patterns and practices in .NET: the Service Locator anti-pattern
August 8, 2013 10 Comments
Introduction
The main responsibility of a Service Locator is to serve instances of services when consumers request them. The pattern is strongly linked to Dependency Injection and was introduced by Martin Fowler here.
The most common implementation of the pattern introduces a static factory. This factory can be configured with concrete services in the composition root of the application, such as global.asax, Main, etc., depending on the type of the application you’re developing. In other words the configuration happens before the first consumer can use it to extract a concrete service. Here you can think of a service as roughly equal to a dependency: the CustomerController has a dependency on ICustomerService. CustomerService has a dependency on ICustromerRepository etc. So when a concrete implementation of the abstraction is needed then the caller tries to grab it from the Service Locator.
A Service Locator is quite similar to Inversion-of-Control (IoC) containers at first. If you’re familiar with some IoCs such as StructureMap or CastleWindsor, then you’ll know that you can register your concrete types in the composition root. In StructureMap you can do this explicitly as follows:
x.For<ICustomerService>().Use<ConcreteCustomerService>();
The Service Locator configuration starts off in a similar manner. It’s essentially a dictionary of abstractions and their desired concrete types: ICustomerRepository – CustomerRepository; IProductService – ProductService. This is perfectly legitimate to do from the composition root. As we will see later consulting the service locator elsewhere in the application for concrete services is an anti-pattern.
Demo
We’ll simulate a dependency between the CustomerService and CustomerRepository classes where CustomerService requires a customer repository to consult the database for queries on the customer domain. Open Visual Studio and add the following standard generic implementation of a ServiceLocator:
public static class ServiceLocator { private readonly static Dictionary<Type, object> _configuredServices = new Dictionary<Type, object>(); public static T GetConfiguredService<T>() { return (T)ServiceLocator._configuredServices[typeof(T)]; } public static void Register<T>(T service) { ServiceLocator._configuredServices[typeof(T)] = service; } }
This is a very minimalistic implementation of the Service Locator. It’s void of exception handling, guard clauses, loading the dependency graph from an XML file but those features only add noise to the main discussion. The dependency map is stored in the private dictionary and the Register method is used, as you’ve probably guessed it, to register dependencies. It is analogous to the .For and .Use extension methods in the StructureMap example above. Let’s add the following interfaces and classes to see how the locator can be used:
public class Customer { }
public interface ICustomerService { Customer GetCustomer(int id); }
public interface ICustomerRepository { Customer GetCustomerFromDatabase(int id); }
public class CustomerRepository : ICustomerRepository { public Customer GetCustomerFromDatabase(int id) { return new Customer(); } }
public class CustomerService : ICustomerService { private ICustomerRepository _customerRepository; public CustomerService() { _customerRepository = ServiceLocator.GetConfiguredService<ICustomerRepository>(); } public Customer GetCustomer(int id) { return _customerRepository.GetCustomerFromDatabase(id); } }
You should be able to follow along this far. The CustomerService class resolves its own dependency using the ServiceLocator. You can configure the locator in Main as follows:
static void Main(string[] args) { ServiceLocator.Register<ICustomerRepository>(new CustomerRepository()); Customer c = new CustomerService().GetCustomer(54); }
Main represents the composition root of a Console application so that’s where you can register the dependency graph. Step through the app with F11 and you’ll see that CustomerRepository is registered and retrieved as expected.
The CustomerService class can resolve its own dependency on ICustomerRepository, so what’s the problem? We can register our concrete implementations, retrieve the stored implementation where it’s needed, register Mock objects as concrete types in a Test Driven Design scenario, program against abstractions, write maintainable code, support late binding by changing the registration, so you’re a happy bunny, right? You shouldn’t be as the ServiceLocator class has a negative effect on the re-usability of the classes that consume it:
- The ServiceLocator dependency will drag along if you try to re-use a class with a call to the locator
- It is not obvious for external clients calling CustomerService() that Dependency Injection is used
The CustomerService will loosely depend on CustomerRepository through the ICustomerRepository interface. This is perfectly legitimate and valid. However, it will be tightly coupled to the ServiceLocator class. Here’s the dependency graph:
If you want to distribute the CustomerService class then you’ll have to attach the ServiceLocator class to the package. It must come along even if the person that wants to use your class is not intending to use the ServiceLocator class in any way because they have their own Dependency Injection solution, such as StructureMap or CastleWindsor. Also, the consumer will need to set up ServiceLocator in the composition root otherwise they will get an exception. As the ServiceLocator may well reside in a different module, even that module must be redistributed for the CustomerService to be usable.
ProductService forces its users to follow the Dependency Injection strategy employed within it. There’s no room for other strategies unfortunately. Developers must simply accept the existence of the service locator. Also, there’s no way of telling that there’s a direct dependency just by looking at its signature which is what consumers will see first when creating a new CustomerService object. If the consumer doesn’t set up ServiceLocator appropriately then they will get an exception when using the CustomerService constructor. Depending on the exception handling strategy all they may get is a KeyNotFoundException. The consumer will then ask the questions: what key? Why is it not found? What are you talking about? WHY YOU NO WORK!!??? The consumer must know about the internals of the ConsumerService class which usually indicates a higher-than-desired level of coupling.
We can therefore rule out this patterns as it brings with it a fully redundant dependency which we can get rid of easily using constructor injection:
public CustomerService(ICustomerRepository customerRepository) { _customerRepository = customerRepository; }
There’s simply no advantage with this pattern that cannot be solved with an alternative solution such as constructor injection coupled with an IoC container. ProductService as it stands is not self-documenting. Its signature does not reveal anything about its dependency needs. Imagine that you download this API from NuGet and call CustomerService service = new CustomerService(). Your assumption would be that this is a fairly simple class that does not have any external dependencies which is not true as it turns out.
You can misuse IoC containers in the same way actually. It’s fine to use IoCs to resolve your dependencies “behind the scenes” but they – or at least some of them – allow the users to fetch concrete types from the container. In StructureMap you’d do it as follows:
StructureMap.ObjectFactory.Container.GetInstance<CustomerRepository>()
You should avoid using this type of dependency resolution for the same reasons why you wouldn’t use a ServiceLocator and its GetConfiguredService method.
Note that this pattern being an anti-pattern is a controversial topic. You can check out this post that offers another viewpoint and argues that Service Locator is indeed a proper design pattern.
View the list of posts on Architecture and Patterns here.
Pingback: Soci blog » Blog Archive » Service locator pattern design kérdés
Pingback: Soczó Zsolt Szakmai blogja
Pingback: object, Object, Boxing-Unboxing, Upcasting-Downcasting and Difference between Type t and object | Noob
I vehemently disagree with the premise that service locator itself is an anti-pattern. The service locator is an invaluable pattern when it is needed, and it fulfills roles that there are no other solutions to, other than poor implementations of the service locator pattern.
Do people apply it in less then optimal scenarios? Absolutely. Does that make a pattern, an anti-pattern? Absolutely not.
The single role of the service locator is you need to locate… a service, at run time based on data. DI is great but it relies on build time resolution… unless you just bury service location in some voodoo part of the container. Service locators are by definition the absolutely lowest amount of coupling you can ever have in code other than zero coupling which means total isolation. Creating cohesive code with a service locator is difficult because of how amazingly low the coupling is, that is why i would never ever recommend burying locator magic inside of a container’s instantiate routine. If you need service location you want it to be able to be stepped through directly inline, not some magic proxy/adapter injected into the constructor that invoked returns you the service instance with absolutely zero information on why it’s that service.
Hi Chris,
Could you provide an example where service locator is used properly? Using it to hand out concrete implementations of abstract dependencies in a class is definitely incorrect.
//Andras
The most obvious use case is SOA / Message flow dependent upon data in the message you pass it to a different handler. The key part is data only known at run time.
There is literally no way to build this without service location. You either build service location with a horrible flow of if/else soup, a giant switch block, or you choose to eliminate reusability and copy the orchestration for each unique routing permutation of the data contract.
ok, thanks for your input. //Andras
Both IoC and Service Locator have their merits. In light of this article, I’d like to point out a couple areas where IoC is limited.
1) Constructor injection is not always possible.
2) IoC has performance costs and thus is not suitable for app that requires micro-optimization.
3) Whenever reflection is being heavily used, all kinds of voodoo magic break loose. As a result, debugging experience will suffer.
I find using IoC to be pleasant in general-purpose frameworks where there’s the luxury to enforce specific conventions and setups. Plain-old Service Locator stays flexible in all other situations.
dotnetchris – actually you can build that without a service locator. Just about every IoC platform can handle injection based on run time data. An abstract factory might be a good choice and avoid the service locator. I’ve never found a scenario where a service locator couldn’t be replaced and as a result makes the code simpler / cleaner.
David Tang – 1) True, but rare. Still you can use property injection or in a worst-case go back to the container. 2) No – implementations may have performance costs that you need to be aware of. Most frameworks will allow you to configure how their initial map creation to avoid performance issues. 3) There are plenty of IoC frameworks that don’t use reflection. It’s absolutely not a requirement for them to implement dependency injection.
That’s called service location.