The Artima Developer Community
Sponsored Link

Weblogs Forum
Service Locator Pattern Revisited, Part 1

11 replies on 1 page. Most recent reply: Oct 7, 2008 1:25 PM by Patrick May

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 11 replies on 1 page
Ian Robertson

Posts: 68
Nickname: ianr
Registered: Apr, 2007

Service Locator Pattern Revisited, Part 1 (View in Weblogs)
Posted: Sep 13, 2008 11:21 PM
Reply to this message Reply
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.


Morgan Conrad

Posts: 307
Nickname: miata71
Registered: Mar, 2006

One more suggestion for improvement Posted: Sep 15, 2008 2:23 PM
Reply to this message Reply
If there is no annotation defining the default class, most projects could provide default logic for finding the class, e.g.

1) take the interface name, either remove the leading "I" or add a trailing "Impl".
2) make some reasonable guess as to which package they will be in, e.g. take the interfaces package and add ".impl". not totally robust, but works much of the time.

Carson Gross

Posts: 153
Nickname: cgross
Registered: Oct, 2006

Re: Service Locator Pattern Revisited, Part 1 Posted: Sep 15, 2008 11:53 PM
Reply to this message Reply
A downside to this approach is that it isn't very discoverable. Users have to know what interface to pass in, which can be hard to find. If you have explicitly named static getters wrapped around a core like you describe, you can easily swap the dependencies for testing (if you think that sort of thing is a good idea) while allowing occasional users to still use the magic of code completion-based discovery.

Also, and I'm sure you are going to address this in a more buffed out implementation, killing the sync'ing in the get() method is crucial for highly-concurrent systems. We've always used double-checked locking (yeah, yeah) and ConcurrentHashMaps to get decent scaling out of caches like this. Any other ideas?

Cheers,
Carson

Ian Robertson

Posts: 68
Nickname: ianr
Registered: Apr, 2007

Re: Service Locator Pattern Revisited, Part 1 Posted: Sep 16, 2008 10:38 AM
Reply to this message Reply
> A downside to this approach is that it isn't very
> discoverable. Users have to know what interface to pass
> in, which can be hard to find. If you have explicitly
> named static getters wrapped around a core like you
> describe, you can easily swap the dependencies for testing
> (if you think that sort of thing is a good idea) while
> allowing occasional users to still use the magic of code
> completion-based discovery.

You raise a good point here. I could see a good case for some classes with static get methods which forward to the core for a set of related services. That said, in practice we haven't found this to be that big of an issue. Sometimes good package-level and library-level javadocs goes a long way towards aiding in discoverability.

> Also, and I'm sure you are going to address this in a more
> buffed out implementation, killing the sync'ing in the
> get() method is crucial for highly-concurrent systems.
> We've always used double-checked locking (yeah, yeah) and
> ConcurrentHashMaps to get decent scaling out of caches
> like this. Any other ideas?

We do use a safe form of double-checked locking. We also don't actually synchronize on the interface class; not only does that make for a bottle-neck, but you also cannot prevent someone else from holding a lock on that class and causing deadlock. Our current approach is to have a second ConcurrentHashMap which holds mutex objects. It's a bit tricky; you need to synchronize on a mutex you create, then putIfAbsent that mutex into the map. If you find another mutex was already there, you then need to synchronize on that mutex. I didn't include that in this article primarily because, while possibly interesting to some people, it's really a separate topic.

Morgan Conrad

Posts: 307
Nickname: miata71
Registered: Mar, 2006

Re: Service Locator Pattern Revisited, Part 1 Posted: Sep 16, 2008 5:06 PM
Reply to this message Reply
Carson/Ian, not sure I understand Carson's point. As I understand, you are comparing (Using I for interface notation)

Case #1

I need an IFoo.

IFoo foo = Bordello.getInstance(IFoo.class);

vs. Case#2, a static

Foo foo = Foo.getInstance();

In both cases
1) I need to figure out, somehow, that what I need is a Foo or IFoo. This may be tricky in a complex system.
2) At that point, auto-completion works.
3) The only diff is that for version#1 I need to remember to use the Bordello.

Chris Kent

Posts: 3
Nickname: cjkent
Registered: Dec, 2007

The service locator pattern is badly flawed Posted: Sep 17, 2008 1:33 AM
Reply to this message Reply
The service locator pattern has a serious problem - you have no way of knowing a class's dependencies without looking at the source and seeing what it looks up in the service locator. With dependency injection there can be no doubt because the dependencies are on the API as setters or (better) as constructor parameters. If you're writing a unit test for a dependency injected class it's much easier to figure out what mocks or dummy implementations you need to supply.

This article is talking about the singleton pattern but the point it makes about it encouraging misleading APIs applies equally to the service locator:

http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/

Ian Robertson

Posts: 68
Nickname: ianr
Registered: Apr, 2007

Re: The service locator pattern is badly flawed Posted: Sep 17, 2008 8:53 PM
Reply to this message Reply
> The service locator pattern has a serious problem - you
> have no way of knowing a class's dependencies without
> looking at the source and seeing what it looks up in the
> service locator. With dependency injection there can be
> no doubt because the dependencies are on the API as
> setters or (better) as constructor parameters.

But isn't an API supposed to expose what a class does, not how it does it? For example, HelloService were to change to not refer directly to System.out, but instead obtain an output stream from a ConsoleService, that shouldn't be something HelloClient needs to worry about.

> If you're writing a unit test for a dependency injected
> class it's much easier to figure out what mocks or dummy
> implementations you need to supply.

In our shop, this tends not to be an issue, since unit tests for a class are generally written by the class's author at the same time as the class itself is being written. Of course, if that's not the case, then there are additional challenges (regardless of whether you use SLP or DI).

In any event, I'm not convinced that there is that much of a gain in "documenting" the services used by a class in its API. Whether you have too look for calls to the service locator in the source, or getters and/or constructor parameters, it's still a convention that needs to be followed. Moreover, even after knowing what services a class depends on, there's still the task of finding out how those services are used, so that you can appropriately mock them. I cannot quite see how one could do that without looking at the source for the class under test.

> This article is talking about the singleton pattern but
> the point it makes about it encouraging misleading APIs
> applies equally to the service locator:
>
> http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/

Thanks for bringing this up. It makes me realize that I forgot a key method when summarizing the Bordello class:
  public static void reset() { 
    services.clear();
  }

While this method is not meant to be called in production, a best practice when writing unit tests for Bordello clients is to always call Bordello.reset() in the test setup method. This ensures isolation from other tests.

Erik Price

Posts: 39
Nickname: erikprice
Registered: Mar, 2003

Re: Service Locator Pattern Revisited, Part 1 Posted: Sep 19, 2008 7:09 AM
Reply to this message Reply
Don't ask. Tell.

Chris Kent

Posts: 3
Nickname: cjkent
Registered: Dec, 2007

Re: The service locator pattern is badly flawed Posted: Sep 19, 2008 7:21 AM
Reply to this message Reply
Perhaps "API" was the wrong term to use. You're right, the API definitely shouldn't expose the implementation details. The service API should be an interface that defines the service's functionality. But that doesn't mean the implementing class can't provide constructors or setters that make its dependencies explicit.

To me the constructor is the ideal place to express a class's dependencies. It's more than just a convention as the compiler makes sure all dependencies are satisfied so there can be no doubt. Using the constructor also ensures that the object can't be in an invalid state with some unsatisfied dependencies which is a weakness of using setters.

If your example used DI the HelloClient wouldn't see any difference if HelloService refered to System.out, ConsoleService or anything else. HelloClient would simply have an object implementing HelloService injected into its constructor.

Last but definitely not least, DI containers offer a lot more beyond simply satisfying dependencies. The most obvious is the ability to insert interceptors between the client and your service and reduce boilerplate by (for example) managaging DB connections, sessions and transactions and allowing cleaner handling of exceptions. My personal favourite is Google Guice, it leaves you with extremely clean code, minimal boilerplate and uses no XML whatsoever.

Ian Robertson

Posts: 68
Nickname: ianr
Registered: Apr, 2007

Re: The service locator pattern is badly flawed Posted: Sep 19, 2008 11:00 PM
Reply to this message Reply
> To me the constructor is the ideal place to express a
> class's dependencies. It's more than just a convention as
> the compiler makes sure all dependencies are satisfied so
> there can be no doubt. Using the constructor also ensures
> that the object can't be in an invalid state with some
> unsatisfied dependencies which is a weakness of using
> setters.

How much value is there in a class expressing its dependencies in the constructor? After all, why should anyone (other than the writers of unit tests) care what the dependencies are? It's an implementation detail. And if you want to know implementation details, you should be reading the source to find out not just which services a class is dependent on, but what the nature of those dependencies are. I don't mean to say that there is no value here; I just haven't seen enough examples to convince me that it's worth making a decision on to adopt a particular architectural style.

> Last but definitely not least, DI containers offer a lot
> more beyond simply satisfying dependencies. The most
> obvious is the ability to insert interceptors between the
> client and your service and reduce boilerplate by (for
> example) managing DB connections, sessions and
> transactions and allowing cleaner handling of exceptions.

You raise a good point. It's worth noting, however, that AOP-style interceptors are not specific to DI; it's just something that some DI frameworks throw in "for free" . A SLP framework could just as easily do AOP proxy wrapping of the services it hands out; one could also use AspectJ.

The issue of managing database resources (connections, sessions, and transactions) is an interesting one. An increasingly popular approach has been to demarcate transactions at the method level (perhaps with annotations). This strikes me as a pattern that has arisen due to a lack of closures. I personally prefer the more explicit approach seen in, for example, Spring's JDBC template. I suspect that if it were not for the syntactic pain involved, this approach would have been popular enough that there would have been little need for container managed transactions.

Chris Kent

Posts: 3
Nickname: cjkent
Registered: Dec, 2007

Re: The service locator pattern is badly flawed Posted: Sep 20, 2008 3:15 AM
Reply to this message Reply
> You raise a good point. It's worth noting, however, that
> AOP-style interceptors are not specific to DI; it's just
> something that some DI frameworks throw in "for free" . A
> SLP framework could just as easily do AOP proxy wrapping
> of the services it hands out

Agreed. In fact I've just implemented exactly that scheme while refactoring a project that had Singletons and ServiceLocators too deeply ingrained to move completely to DI. Although behind the scenes the ServiceLocator delegates to Spring for AOP.

> The issue of managing database resources (connections,
> sessions, and transactions) is an interesting one. An
> increasingly popular approach has been to demarcate
> transactions at the method level (perhaps with
> annotations). This strikes me as a pattern that has
> arisen due to a lack of closures. I personally prefer the
> more explicit approach seen in, for example, Spring's JDBC
> template. I suspect that if it were not for the syntactic
> pain involved, this approach would have been popular
> enough that there would have been little need for
> container managed transactions.

Again, I completely agree. Every useful application I've found for AOP is a workaround for Java's lack of closures and verbosity. I've been playing around with those kind of constructs in Scala and it's such a pleasure to have enough power in the language to not need a framework.

Patrick May

Posts: 3
Nickname: pjmay
Registered: Jun, 2003

Re: Service Locator Pattern Revisited, Part 1 Posted: Oct 7, 2008 1:25 PM
Reply to this message Reply
This problem has been solved very elegantly by Jini (http://www.jini.org). Have you considered using that?

Patrick

Flat View: This topic has 11 replies on 1 page
Topic: The Scrum Blog Previous Topic   Next Topic Topic: The Weblog Skeptic

Sponsored Links



Google
  Web Artima.com   

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