Is the Service Locator an anti-pattern?

Recently, I’ve been reading a number of articles arguing whether the Service Locator pattern is in fact an anti-pattern. On one side of the fence, there are those that argue yes, such as Service Locator is an Anti-Pattern by Mark Seemann and Service Locator is Indeed an Anti-pattern by Nick Hodges. There are those that argue the opposite though, such as Service locator is not an anti pattern, by Jonas Gauffin.

It’s clearly a topic that invokes strong opinion on both sides. So is this just another case of a standards war, such as tabs vs spaces, or how to lay out braces? I’d say no, arguments over layout just centre which approach leads to the most readable code (the correct answer to that of course being “the style you are most used to”). The arguments over the Service Locator is more complicated as a read of the above links, plus their comments and answers, will show. There’s no simple one style versus another argument going on here; it’s a clash of development practices.

So what is a service locator? In its must basic form, it’s a static, globally accessible, store of arbitrary services:

The advantage of such a pattern should be clear: numerous services can be registered and accessed from disparate parts of the system without the need for complicated dependencies being passed around. The disadvantages ought to be clear too:

  1. The system becomes subject to the whims of timings. For example, if part of the system tries to access a service before it’s registered, it’s likely to crash and burn. Finding the cause of such a bug can be hard.
  2. Any part of the system can access any service. This creates complicated coupling between parts of the system. Change a service and it becomes very difficult to know whether that change will break any other part.

Effectively, our service locator is a glorified global variable. It’s easy to create, but it causes problems later on. The standard way these days to avoid introducing problems when making changes is to create tests. Our service locator is a huge barrier to unit testing too though. Even if we introduce a method to allow the list of services to be cleared between each test, the tests must be run sequentially as two parallel tests could cause random failures when each tries to set up its own list of services.

One solution to this is to remove the static state of the service locator: to make it a service locator instance. This in turn causes a new problem: we can end up with more than one instance and so different parts of the system might have access to different lists of services. We could even end up with two competing copies of the same service, which might result in any number of random bugs in the system. In turn, the common solution to this problem is to make the service locator instance a singleton, accessed via a static method call. And then we are back to square one of having a glorified global variable once more.

It’s for these sorts of reasons that fans of dependency injection (DI) favour “inversion of control” (IoC). However, what this often equates to is a glorified service locator instance (called a container) that understands the interconnectedness of the system. That container can both create object instances (services) when required and inject dependences (often via using the container itself) into these created instances. Now I should be clear here that some advocates of IoC strongly argue against using it this way. However, as Jonas Gauffin points out in his argument for service locators, many examples used by the providers of many IoC solutions do exactly this.

Let’s be clear here: in my world at least, two complementary practices share the title of “king of coding practices”. One is readability and the other, ease of testing. So let’s judge the service locator against those. As Mark Seeman explains in his post, service locators can hinder intellisense, so that could be an argument against them. However, dependencies injected throughout the code can make the code harder to read compared with a globally available service. I don’t think there’s a clear winner for or against the pattern when it comes to readability.

Ease of testing is much more clean cut though. Tests should be quick and simple to write. Having any global state makes testing hard. Having complex dependencies, such as a service locator that handles complex services, requires lots of mocking and setup to run tests and so makes testing hard. Minimising how dependent a module of code is on other modules is always a winner when writing tests. When it comes to testing, there is a strong case against the service locator pattern (and thus misused IoC too).

Does the fact that the service locator pattern hinders testing make it an anti-pattern? Well no, as there are likely edge cases where a simple service locator internal to, for example, a collection of classes within a project/dll that makes the code simpler without hindering testing. I recently came across a similar edge case around the infamous singleton pattern. Within Succinc<T>, I needed a way of representing
none for option types. After various experiments, I opted for a singleton:

It doesn’t have any state; it’s a simple immutable value. It makes no sense for two instances of none to exist, so there is only ever one. It’s easy to read and understand and it doesn’t hinder writing tests in any way. It passes both of my “king of the practices” test. If I’d taken the “singletons are anti-patterns” stance, I would have had to abandon pragmatism and use a less efficient solution instead.

If the service locator isn’t an anti-pattern in all cases, does that mean the matter is done and dusted? No. To explain why, let’s look at what an anti-pattern is:

An AntiPattern is a pattern that tells how to go from a problem to a bad solution.

An anti-pattern (or antipattern) is a common response to a recurring problem that is usually ineffective and risks being highly counterproductive.

The service locator (and singleton) patterns are often the cause of these bad solutions. The fact that a service locator might be making those tests hard to write is a symptom of that anti-pattern. The pattern might be the cause; it might be the symptom, but I’d argue (in a change of opinion from what I’ve stated in the past) that it’s not actually an anti-pattern itself. The real anti-pattern is writing code that is either hard to read or hard to test. Such practices really are a case of creating a bad solution to a problem and are highly counter-productive. Writing hard-to-test code is definitely an anti-pattern. That, I’m sure, is something every competent developer really can agree on.

6 thoughts on “Is the Service Locator an anti-pattern?

  1. @reddy,

    What is a “true object-orientated application” supposed to be?

    Sorry to burst your bubble, but your article most certainly does not do anything like explain why “the service locator pattern is far from being an anti-pattern”. Sadly it simply highlights a number of basic mistakes in your approach to software design, such as introducing a tighty coupled, circular dependency between Package and CourierService.

    It’s good that, as a beginner, you are thinking about these things and blogging about your ideas. Be careful you do not disappear down the “expert beginner” rabbit hole though.

  2. I won’t reply to the “bubble” and “beginner” aggressions, but I think my article and my explanations are very clear. However I’m not surprised you say that as your remark on the Package and the CourrierService class shows that you missed the essence of it, probably too busy down reading it.

    Thanks for your input, but I have to say I find your lifestyle advices inappropriate and am very happy with what I do.

Comments are closed.