The Artima Developer Community
Sponsored Link

Code by Any Other Name
Service Locator Pattern Revisited, Part 2
by Ian Robertson
September 17, 2008
Summary
Once the boilerplate traditionally associated with the Service Locator Pattern is eliminated, one of the key motivations for Dependency Injection is reduced, if not eliminated. This article, the second of a two part series, looks at the pros and cons of each approach to interacting with services.

Advertisement

In Part 1, we looked at Bordello, a simple implementation of the Service Locator Pattern to aid in making code more testable. Of course, the Service Locator Pattern isn't the only way to accomplish this; using a Dependency Injection framework such as Spring or Guice is another popular technique. One reason for the popularity of Dependency Injection is the perceived difficulty associated with the Service Locator Pattern. Indeed, Crazy Bob Lee touts this as one of the motivations for Guice:

Before Guice, I had to make the same up-front decision over and over: do I call a constructor directly, or do I write a factory and call it instead? If you start out calling a constructor and later decide that you need a factory, you have to go back and change all the callers. On the other hand, factories come at a cost: they clutter your API and result in a bunch of boilerplate code which you have to both write and maintain.

As we saw in Part 1, it's actually quite easy to have a lightweight Service Locator which doesn't require writing new factories for each service. To quickly review, client code simply calls Bordello.get to acquire services as it needs them.

public class HelloClient {
  public void greetFormally(String surname) {
    Bordello.get(HelloService.class).sayHello("Sir " + surname);
  }
}
By contrast, a client using Guice will have services injected at construction time, and might look like:
public class HelloClient {
  @Inject
  private HelloService service;

  public void greetFormally(String surname) {
    helloService.sayHello("Sir " + surname);
  }
}

Comparing Bordello with Dependency Injection

On the surface, DI frameworks and this sort of "generic" service locator seem very different. It turns out, however, that while the two approaches feel quite different, their behavior in most ways is strikingly similar. Both use interfaces to enable testability. Both require some means of specifying the preferred implementation of a service; Bordello uses annotations for this (as does Guice, and later versions of Spring), but XML or other code-based configuration could just as easily be used, as could a convention-over-configuration approach. To determine what DI services a class uses, you look for member variables which are flagged for injection; to determine the Bordello services used, look for occurrences of Bordello.get.

There, however are a few notable differences. Perhaps the most obvious is the ways in which each approach ties code to the framework. On the plus side for DI, a typical DI client might not have any reference to the framework which will be used to inject its dependencies. As such, client code has no dependency on the DI framework. In contrast, a client relying on Bordello will have a reference to the Bordello class. In practice, I'm not convinced that this is that great of a cost; I've not heard any cases of a project switching DI frameworks in midstream. However, while a class can reference a client which uses Bordello and be none the wiser, typically to use a client which relies on DI, one must have the DI framework provide that client. While this may not create dependencies in a formal sense, it does place certain restrictions on how one can go about acquiring instances.

Another impact of using DI is the requirement that a member variable be declared for each injected service; depending on one's style preferences, a setter or constructor argument might be required as well. While this is a fairly lightweight requirement as boilerplate goes, it does have a couple undesirable side effects. The first effect is that (in the case of using setter methods or constructor arguments) the services a class chooses to interact with now become part of its API. The fact that a class is a client of HelloService is an implementation detail, and should not need to be advertised. The second effect is that all of a class's dependencies must be instantiated when an instance of that class is first created. Because of the transitive nature of DI, this effectively means creating a whole object graph upon startup. On the up side, this means that a DI framework can catch certain problems with circular or undefined dependencies early rather than late. On the down side, if you want to lazily construct a service on an as-needed basis, you are forced to go back to the Factory Pattern, and just inject a factory for your service, rather than the service itself. Notably, if that service has its own requirements, the factory may need to explicitely reference the DI framework in order to inject dependencies.

A nice feature of Bordello is that it is self-managing; no start up code is required. While DI frameworks generally require some initial bootstrap work to create a container, Bordello is designed to create service instances upon first request, with no prior initialization work. This is particularly handy for libraries which want to avoid creating any unnecessary usage requirements for their clients.

One thing that Bordello does not attempt to do is provide multiple different implementations of a service in production. This could certainly be done; for example, an @Implementors annotation could contain multiple @Implementation annotations, each distinguished by some sort of key. At my company, however, we have simply found very little need for this. Notably, we do not produce shrink-wrapped software which would need to be deployed in a variety of environments, so we don't need to, for example, support multiple different ORM strategies.

An area easily addressed by both approaches is configuration of services. For example, one can have Spring configure a service by providing configuration information in their beans.xml file, which Spring will then inject into their instances. With Bordello, configuration is up to the instance itself; it helps therefore to have a ConfigurationService which services can use; this service of course can be acquired through Bordello.

Conclusion

Despite the cosmetic differences between the Service Locator Pattern and Dependency Injection, the two are functionally quite similar. The biggest difference between them is not what can be accomplished, or even accomplished easily, but rather the style of programming that each requires. The Service Locator Pattern is a code-focused approach; when an instance of a service is needed, code explicitely calls out to a service locator to obtain it. In contrast, Dependency Injection is a more extralinguistic approach, providing clients their dependencies without any explicit action on the clients behalf.

Talk Back!

Have an opinion? Readers have already posted 12 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