Service Locator is not an anti-pattern

Several years ago I began using the Locator as a simplified way to break dependencies. I had read numerous articles on the subject and bore witness to "abuse" of common IoC Containers. This lead me down the path of using the Service Locator pattern since I believe it's a great tool for simple applications.

In this article I'm going to defend the Service Locator pattern for what it is, a simple way to decouple dependencies. There is a lot of angst towards this pattern. I encourage you to do your own research. I will also discuss its limitations, benefits of use, and my implementation of it.

Service Locator Example

For starters the Service Locator pattern is a simple pattern that is a central place for resolving dependencies in your application. Martin Fowler, has a good write up of the pattern.

I used a simple implementation that stores singletons for each dependency. Because it stores a singleton, the implementation must be thread-safe. This works well for simple things like Data Mappers or Email Services.

///
/// Service locator
///
/// The service
public sealed class Locator  
{
    ///
    /// Singleton holder
    ///
    private static Service singleton;

    ///
    /// Gets a singleton instance of the specified service.
    ///
    /// Throws exception if SetInstance has not been performed.
    /// The instance
    public static Service GetInstance()
    {
        if (singleton == null)
            throw new ApplicationException("Service is not set: " + typeof(Service).FullName);

        return singleton;
    }

    ///
    /// Sets the instance of the service.
    ///
    ///
    public static void SetInstance(Service instance)
    {
        singleton = instance;
    }

    ///
    /// Checks if an instance exists
    ///
    /// True if an instance exists, otherwise false
    public static bool HasInstance
    {
        get { return singleton != null; }
    }
}

Registering dependencies is as simple as calling the Set method with an implementation of your service interface.

Locator<IPostMapper>.SetInstance(new MySqlPostMapper());  

Using the Locator is extremely simple. You call the Locator's GetInstance method and retrieve the service.  Done!

public class PostWithLocator : IPost  
{
    ///
    /// Saves the post
    ///
    public void Save()
    {
        Locator<IPostMapper>.GetInstance().Save(this);
    }
}

Now that you've seen how to use it.  I'll discuss some of the controversies surrounding this pattern.

Is it an anti-pattern?

Personally, I don't think so.  There are some issues with it, but just because you can hurt yourself with a tool does not make it a bad one. I'm a big proponent of this pattern for simple applications. I believe it is a simple alternative to dependency injection which tends to increase the amount of code your write in order to do it correctly.  Combined with a simply pattern like Active Record it has served me very well.

Martin Folwer believes there is a use for it as well and describes the fundamental difference between the Service Locator and Dependency Injection:

The important difference between the two patterns is about how that implementation is provided to the application class. With service locator the application class asks for it explicitly by a message to the locator. With injection there is no explicit request, the service appears in the application class - hence the inversion of control.

Martin Fowler - Service Locator vs Dependency

He goes on to explain many of the differences and uses for each.  It is a fantastic write-up on the subject that I encourage you to read.

So now to get to brass tax.  Here are my perceived pro's and con's!

PRO - It is simple to code

I don't have to use constructor injection, property injection, or worry about abstracting dependencies graphs with factories.  When I need something, I use the Locator to find it.  It is the central point I go in my application for dependencies.  For simple apps, this makes sense.

PRO - It is simple to understand

Understanding that all dependencies resolve through a central point is a fairly easy concept to grasp.  Hit the Locator.  That's as easy as it gets.

Many opponents will claim that use of Locator is more difficult to understand because it is unclear what dependencies an object has without  looking through the code.  This is true.  However, if you're working with simple code this isn't an issue. If you think you may run into an issue down the line, don't use it, or refactor it out when you need to.

PRO - Look Ma, Unit Testing

You can Unit Test with Locator just as simple as with an IoC Container or direct injection.  Both are techniques for decoupling dependencies. Either way you need to specify the dependencies.  With Locator, you set mocks into the Locator and they will get resolved when requested.

For example, mocking out a save operation with the Moq framework:

var mockPostMapper = new Mock<IPostMapper>();  
mockPostMapper.Setup(p => p.Save(post)).Returns(100);  
Locator<IPostMapper>.SetInstance(mockPostMapper.Object);  

CON - Dependency on your Locator

One of the biggest issues is that you swap out numerous dependencies for a single dependency on your Locator.  While reducing the dependencies is a good thing, it still leaves you classes tainted with "infrastructure code".  Removing this infrastructure code requires constructor injection or property injection.

CON - Not so great for APIs

Because dependencies are hidden inside your class and because they are specified at runtime, formal knowledge of the dependencies is required. Again though, for simple or internal projects this is acceptable.  You have the ability to view the code to ensure all dependencies have bindings.  When releasing an API to the public, this pattern falls on its face. Users of the API would need to refer to documentation or find out via trial and error what dependencies need to be specified.

CON - No context awareness

One of the major draw backs is due to the very nature of the pattern. Because there is only a single instance of the Locator there can only be one resolution and because you are REQUESTING a service instead of having a service INJECTED into your code, it makes it impossible  to have two different behaviors for the same code executing simultaneously.

For example, what if the code for the users of my site uses caching, but the administration portion of my site does not use caching.  To set a service to use my caching layer I would do something like this:

// The caching mapper wraps the database mapper
Locator<IPostMapper>.SetInstance(new MemcachedPostMapper(new MySqlPostMapper()));  

For direct access to the database I would register it by doing something like this:

// Database mapper
Locator<PostMapper>.SetInstance(new MySqlPostMapper());  

This would be consumed by doing the following

public void Save()  
{
   Locator<IPostMapper>.GetInstance().Save(this);
}

I can't simultaneously use both of those registrations in my application at the same time. It's either one or the other registration that is currently active. Coding around this issue involves injecting a specific Locator instance or usage context into your objects. This falls into the weird Locator/IoC hybrid pattern that is probably is an anti-pattern and is discussed in numerous articles.

Suffice it to say, the Locator should only have a single context and only used to pull in dependencies.

Conclusion

The Service Locator is a very straightforward pattern to use.  It's a simple way to decouple your code.  To me, it is great for simple applications.  Many of the opponents to this pattern view it as something that should never be used.  Similar to my views on Active Record vs DDD patterned  implementations, I think it's a tool in your tool belt.  If you have a simple application, use a simple tool for code decoupling.

comments powered by Disqus