Container (Service Locator) pattern

Container (Service Locator) pattern

The Anti-pattern

Today we'll discuss the service locator or container pattern for dependency resolution.

While Service Locator is a well known pattern (particularly in the Java world). It's considered by many an anti-pattern and that it should be avoided because it hides class dependencies, causing run-time errors rather than compile-time errors. It also makes code hard to maintain because it becomes unclear when you would be introducing changed that break the code.

There's a caveat to this but lets push further ahead on what it is first. In my previous post, I explained Dependency Injection . Container pattern and DI are both trying to achieve the same goal... Loose coupling of the consumer class from its dependencies. There's a popular phrase thrown around in the developer community "New is Glue". And what it means is that having dependency instantiation in the consumer class creates "glue" or strong coupling.

class Foo {
  dependency;
  constructor(Dependency) {
    this.dependency = new Dependency();
  }
}

Class Foo depends on the Dependency class and instantiates it in its own constructor. This is bad news from a Software Architecture standpoint and should be avoided whenever possible.

Service locators as the name states is an artifice that keeps track of and takes care of the instantiation details that are to be used in the consumer class. It works as a middleman between the consumer class and the dependency class.

It is easy to get these two mixed up so the following images should help clear the waters.

Untitled Diagram (1).jpg

The locator "takes" some parameter or service id in order to return instantiated versions of the dependencies. Whereas the Dependency Injection "provides", ( this is why services and providers are used interchangeably in systems that use dependency injection), the instantiated dependencies to the consumer class.

The caveat

Let's keep a few things in mind:

  • Container patterns are usually considered anti-patterns and dependency injection is the preferred way to go today.

  • The two are not mutually exclusive.

Check out the following snippet:

class A {
  constructor(b){
    this.b = b;
  }
}
class B {
  constructor(a) {
     this.a = a;
  }
}

const a = new A();
cons b = new B();

We need B to instantiate A and viceversa. In this case (although there may be other instances where container pattern would be a better fit), the locator can be used to lazy load the depencencies. By lazy loading we mean to get the service when it is needed rather than in the constructor.

I really hope that this article has shed some light on these architectural techniques and patterns. There's always outlier cases and your judgement and experience as a programmer will take precedence. Happy coding!