Needle— to the point --> | Needle Version: 1.3.0 Manual Last Updated: 2005-12-24 15:20 UTC

Previous (2. Registry) | Up | Next (4. Dependency Injection)

# 3. Service Locator

## 3.1. Overview

The service locator design pattern can be considered a subset of dependency injection. Because it is simpler, it is as good of a place to start teaching DI as any.

To demonstrate both techniques, we’ll pretend we’re going to write an online forum application. To start, let’s come up with a rough design by cataloging the components we’ll need.

- `Logger`. This will be used to write messages to a file.
- `Authenticator`. This will be used to validate whether a user is who they say they are.
- `Database`. This encapsulates access to the database that will store our forum data.
- `Session`. This represents a single user’s session.
- `View`. The presentation manager, used to render pages to the user.
- `Application`. The controller that ties it all together.

(Of course, a real online forum application would be significantly more complex, but the above components will do for our purposes.)

The dependencies between these components are:

- `Authenticator` has `Database` (for querying user authentication information) and `Logger`
- `Database` has `Logger` (for indicating database accesses and query times)
- `Session` has `Database` (for storing session information) and `Logger`
- `Application` has `Database`, `View`, `Session`, and `Authenticator`, and `Logger`

## 3.2. Conventional Architecture

A conventional architecture will have each component instantiate its own dependencies. For example, the `Application` would do something like this:

A conventional application implementation [ruby]

1
2
3
4
5
6
7
8
9

    class Application
      def initialize
        @logger = Logger.new
        @authenticator = Authenticator.new
        @database = Database.new
        @view = View.new
        @session = Session.new
      end
    end

However, the above is already flawed, because the `Authenticator` and the `Session` both need access to the `Database`, so you really need to make sure you instantiate things in the right order and pass them as parameters to the constructor of each object that needs them, like so:

A parameterized application implementation [ruby]

1
2
3
4
5
6
7
8
9

    class Application
      def initialize
        @view = View.new
        @logger = Logger.new
        @database = Database.new( @logger )
        @authenticator = Authenticator.new( @logger, @database )
        @session = Session.new( @logger, @database )
      end
    end

The problem with this is that if you later decide that `View` needs to access the database, you need to rearrange the order of how things are instantiated in the `Application` constructor.

## 3.3. Locator Pattern

The service locator pattern makes things a little easier. Instead of instantiating everything in the constructor of the `Application`, you can create a factory method somewhere that returns the new `Application` instance. Then, inside of this factory method, you assign each new object to collection, and pass that collection to each constructor.

Service locator example [ruby]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

    require 'needle'

    def create_application
      locator = Needle::Registry.new

      locator.register( :view ) { View.new(locator) }
      locator.register( :logger ) { Logger.new(locator) }
      locator.register( :database ) { Database.new(locator) }
      locator.register( :authenticator ) {Authenticator.new(locator) }
      locator.register( :session ) { Session.new(locator) }
      locator.register( :app ) { Application.new(locator) }

      locator[:app]
    end

    class Application
      def initialize( locator )
        @view = locator[:view]
        @logger = locator[:logger]
        @database = locator[:database]
        @authenticator = locator[:authenticator]
        @session = locator[:session]
      end
    end

    class Session
      def initialize( locator )
        @database = locator[:database]
        @logger = locator[:logger]
      end
    end

    ...

This has the benefit of allowing each object to construct itself á la carte from the objects in the locator. Also, each object no longer cares what class implements each service—it only cares that each object implements the methods it will attempt to invoke on that object.

Also, because Needle defers the instantiation of each service until the service is actually requested, we can actually register each item with the locator in any arbitrary order. All that is happening is the block is associated with the symbol, so that when the service is requested, the corresponding block is invoked. What is more, by default each service is then cached, so that it is only instantiated once.

Thus, when we get the `:app` service (on the last line), the `Application` constructor is invoked, passing the locator to the constructor. Inside the constructor, `Application` retrieves each of its dependencies from the locator, causing each of them to be instantiated in turn. By this means, everything is initialized and constructed when the `create_application` method returns.

In the interest of brevity, the `create_application` could have been written like this, using a “builder” object (called `b` in the example below) to help register the services:

Service locator example using #define [ruby]

1
2
3
4
5
6
7
8
9
10
11
12

    def create_application
      locator = Needle::Registry.define do |b|
        b.view { View.new(locator) }
        b.logger { Logger.new(locator) }
        b.database { Database.new(locator) }
        b.authenticator {Authenticator.new(locator) }
        b.session { Session.new(locator) }
        b.app { Application.new(locator) }
      end

      locator[:app]
    end

Previous (2. Registry) | Up | Next (4. Dependency Injection)