The Artima Developer Community
Sponsored Link

Code by Any Other Name
Service Locator Pattern Revisited, Part 1
by Ian Robertson
September 14, 2008
Summary
Traditional implementations of the Service Locator Pattern result in a large amount of boilerplate code. This article, the first of a two part series, shows that this is not a necessary weakness.

Advertisement

When designing for testability, it's important to be able to mock out certain classes in order to test other classes which depend on them. The old fashion way of doing this was to use the Service Locator Pattern. The idea was that for each service which one might want to be able to mock, instead of having client code construct the service directly, clients would call a (static) method which would provide an appropriate instance of the service. In a production environment, this would typically return a singleton, but unit tests would be able to change what was returned by calling another (static) method to override what instance was returned.

The problem with traditional implementations of the Service Locator Pattern is that each locator involves a fair amount of boilerplate. At minimum, you need a static "get" method; to enable mockability, you have to add a "set" method, and a variable to bridge between the get and the set. On top of all this, you need to decide where these methods belong. If you put all your locator methods in a single class, that class will quickly become difficult to manage. By contrast, putting each locator method in its own class requires even more boilerplate. Neither approach does much for predictability; at best, there is a convention about where to find a locator method for a given class.

It's possible, indeed quite easy, to have a single locator for all services. Martin Fowler does this with his Dynamic Service Locator pattern, creating a map from String keys to services. He rightly laments, however, that:

On the whole I dislike this approach. Although it's certainly flexible, it's not very explicit. The only way I can find out how to reach a service is through textual keys. I prefer explicit methods because it's easier to find where they are by looking at the interface definitions.

(An additional problem with his locator is that, as with JNDI, it requires the client to do class casts from the locator.)

A type-safe universal service locator.

The problem of deciding what a good key is for each service becomes much simpler when one restricts themselves to the case where, for any service interface, there is a unique implementation of that interface which is meant to be used in production. In this case, the interface class itself makes for a fine key. Taking advantage of the fact that the Class class itself is generic, one can doubly benefit from this choice by avoiding the need for class casting. Add in annotations to configure the default implementation for each service, and you can create a very simple and effective universal means of acquiring services. J. McConnell once observed that an "provider of services" should be called a Bordello, and the name stuck. Here's the main API:

public class Bordello {
  public static <T> T get(Class<T> interfaceClass) { ... }
  public static <T> void set(Class<T> interfaceClass, T providor) { ... }
}

Before looking at a way to implement this, lets look at a simple example of how it is used. First, we have a service interface:

@Implementor(HelloServiceImpl.class)
public interface HelloService {
  public void sayHello(String name);
}

Note the @Implementor annotation here; it defines the standard implementation to be used unless specified otherwise. This implementation follows:

public class HelloServiceImpl implements HelloService {
  public void sayHello(String name) {
    System.out.println("Hello, " + name);
  }
}

A client need only call Bordello.get to get a service implementation:

public class HelloClient {
  public void greetFormally(String surname) {
    Bordello.get(HelloService.class).sayHello("Sir " + surname);
  }
}

Finally, let's look at a unit test for HelloClient. Since the standard HelloService implementation prints to System.out, testing with it is not easy. So we create a mock service implementation which simply verifies its argument is what we expect, tell Bordello to provide that, and then test our client's method:

  @Test public void testGreeting() {
    HelloService mockHelloService = new HelloService() {
      public void sayHello(String name) {
        assertEquals("Sir Smith", name);
      }
    };
    Bordello.set(HelloService.class, mockHelloService);
    new HelloClient().greetFormally("Smith");
  }

A basic Bordello implementation

To do all this, we only need two framework classes - Implementor and Bordello. The annotation has no surprises:

/**
 * Indicates the default implementation for a service.
 */
@Documented
@Target(ElementType.TYPE@Retention(RetentionPolicy.RUNTIME)
public @interface Implementor {
  Class<?> value();
}

The main class is equally straightforward:

public class Bordello {
  private final static Map<Class<?>, Object> services
    new ConcurrentHashMap<Class<?>, Object>();

  /**
   * Acquire an implementation of a service. If one has not already
   * been instantiated, instantiate the class defined by the
   Implementor annotation on the interface
   */
  public static <T> T get(Class<T> interfaceClass) {
    synchronized (interfaceClass) {
      Object service = services.get(interfaceClass);
      if (service == null) {
        try {
          Class<?> implementingClass =
            interfaceClass.getAnnotation(Implementor.class).value();
          service = implementingClass.newInstance();
          services.put(interfaceClass, service);
        }
        catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
      return interfaceClass.cast(service);
    }
  }

  /**
   * Set an alternate service implementation.
   * Typically only called in unit tests.
   */
  public static <T> void set(Class<T> interfaceClass, T providor) {
    synchronized (interfaceClass) {
      services.put(interfaceClass, providor);
    }
  }
}

This is the simplest form; in practice there are number of improvements that can be easily made, from more robust behaviors to supporting additional "scopes" such as instance-per-request and thread-local. Sanjay Acharya has blogged about an alternate approach which, rather than looking at an annotation on the interface to find the preferred implementation, scans the classpath to find the sole implementation. The upshot, however, is that having done this once, no per-service locator code is needed.

In Part 2, I'll compare Bordello to Dependency Injection.

Talk Back!

Have an opinion? Readers have already posted 11 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Ian Robertson adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Ian Robertson is an application architect at Verisk Health. He is interested in finding concise means of expression in Java without sacrificing type safety. He contributes to various open source projects, including jamon and pojomatic.

This weblog entry is Copyright © 2008 Ian Robertson. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use