Service Locator Pattern

Learn how a Service Locator can be modeled in Clojure, why it sometimes works for plugin-style registries, and why explicit dependency injection is usually clearer for ordinary application design.

Service Locator pattern: A pattern where code retrieves dependencies from a central registry instead of receiving them explicitly from the caller.

Service Locator can be implemented easily in Clojure, but that does not mean it should be a default design choice. It is often convenient for plugin registries, runtime lookups, or integration hubs. For everyday application code, explicit dependency injection is usually clearer because it makes dependencies visible instead of hidden behind global lookup.

A Simple Registry Is Easy to Build

1(def services (atom {}))
2
3(defn register-service! [k service]
4  (swap! services assoc k service))
5
6(defn get-service [k]
7  (or (get @services k)
8      (throw (ex-info "unknown service" {:service k}))))

That is enough to demonstrate the pattern:

  • services are registered centrally
  • callers ask the locator for what they need
  • the concrete implementation stays hidden behind the registry

Why Teams Reach for It

Service Locator feels attractive because it can:

  • centralize runtime lookup
  • simplify plugin discovery
  • avoid threading many dependencies through call stacks
  • support optional integrations that may or may not be present

Those are real benefits in some environments, especially when the set of implementations is dynamic.

Why It Is Often a Poor Default

The pattern also hides important design information. A function can look simple while depending on several services that are only discoverable at runtime. That hurts:

  • readability
  • testability
  • local reasoning
  • refactoring confidence

In ordinary application code, explicit injection is usually better:

1(defn send-order-email [mailer order]
2  (mailer order))

The dependency is visible in the function signature. That usually makes the design more honest.

Where the Pattern Still Makes Sense

Service Locator can be appropriate when:

  • plugins are registered dynamically
  • the set of services is discovered at startup
  • integrations are optional and selected at runtime
  • the locator is clearly treated as an infrastructure boundary

In those cases, the registry itself is part of the problem domain of the infrastructure layer, not just a shortcut for avoiding explicit design.

The Real Risk

    flowchart LR
	    A["Caller"] --> B["Service locator"]
	    B --> C["Service A"]
	    B --> D["Service B"]
	    B --> E["Service C"]

The risk is not centralization alone. The risk is that the caller’s real dependencies disappear from plain sight.

Common Failure Modes

Service Locator goes wrong when:

  • the registry becomes global and ubiquitous
  • tests rely on implicit mutable shared registration
  • runtime lookup errors replace compile-time or design-time clarity
  • the locator is used where direct injection would have been simpler

If most business code reaches into a central registry, the codebase usually becomes harder to reason about over time.

Practical Heuristics

Treat Service Locator as an infrastructure pattern, not a default application pattern. Use it for registries and dynamic lookup boundaries when that indirection is real. Prefer explicit dependency passing for ordinary business logic.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026