2. Registry

2.1. Overview

The registry is at the heart of any dependency-injected application or library. All services are registered with the registry, so that when an application needs an instance of a particular service, it may obtain that service reference by querying the registry.

In order to use Needle, you only really need to understand how to create and manipulate registry objects.

2.2. Creating

Creating a registry is as simple as calling Needle::Registry.new. This will give you a new registry object, bootstrapped to contain a few general services.

Creating a registry [ruby]
1
2
3
require 'needle'

registry = Needle::Registry.new

Once you have the reference to the registry, you can register services with it, create new namespaces in it, and so forth.

Alternatively, you can pass a block to #new:

Creating a registry with a block [ruby]
1
2
3
registry = Needle::Registry.new do |r|
  ...
end

The parameter to the block will be a reference to the registry. This allows you to register services with the registry as soon as it is created.

Another convenience method is #define!:

Creating a registry with #define! [ruby]
1
2
3
registry = Needle::Registry.define! do
  ...
end

This block accepts no parameters, and evaluates the block as if it were passed to Registry#define! (see below).

There can be problems with using define!, however, since it uses instance_eval to evaluate the block within the context of another object. If you find yourself running into scoping issues, you might want to consider using #define:

Creating a registry with #define [ruby]
1
2
3
registry = Needle::Registry.define do |b|
  ...
end

This block accepts a single parameter—a “builder” object to aid in registering services—and evaluates the block as if it were passed to Registry#define (see below).

2.3. Services

Registering services with a Needle registry is very straightforward. The simplest way to do it is:

Registering services [ruby]
registry.register( :foo ) { Bar.new }

The above will register a new service with the registry, naming it :foo. When :foo is requested from the registry, a new instance of Bar will be instantiated and returned.

You get services from the registry in either of two ways:

Accessing services [ruby]
1
2
3
4
5
# Treating the registry as a Hash
svc = registry[:foo]

# Treating the service as a property of the registry
svc = registry.foo

Convenience Methods

Because you will often need to register many services with a registry at once, two convenience methods have been provided to make this use case lean and mean.

The first is define. Just pass a block to define that accepts one parameter. This parameter will be a “builder” object that allows you to define services just by sending them as messages to the builder:

Defining services [ruby]
1
2
3
4
5
registry.define do |b|
  b.foo { Bar.new }
  b.bar { Foo.new }
  ...
end

Alternative, you can call define!, passing a block that accepts no parameters. This block will be evaluated in the “builder” object’s context, with any unrecognized method call being interpreted as a new service registration of that name:

Defining services via #define! [ruby]
1
2
3
4
5
registry.define! do
  foo { Bar.new }
  bar { Foo.new }
  ...
end

Both of the above will register two new services with the registry, :foo and :bar.

Default Lifestyle

By default, a service is only instantiated once per registry. This means that (using the above example) if the service :foo were queried twice, the registry would return the same object for both queries:

Demonstrating singleton service behavior [ruby]
1
2
3
4
svc1 = registry.foo
svc2 = registry.foo

p svc1.object_id == svc2.object_id #=> true

You can change this behavior, with service models. See the chapter on Service Models for more information.

Parameterized Services

Needle also supports parameterized services. These are services that, when requested, require contextual information to be passed as a parameter so that the service can be correctly initialized.

Consider the following example, in which some hypothetical Printer class represents any of a number of printers, based on a parameter given to its constructor.

Parameterized services [ruby]
1
2
3
4
5
6
registry.register( :printer, :model => :multiton ) do |c,p,name|
  Printer.new( c.log_for( p ), name )
end

mono  = registry.printer( :monochrome )
color = registry.printer( :color )

There are a few things to note about the above example:

  1. The :multiton model is explicitly requested. This is necessary because the default service model (:singleton) does not allow parameterized services. Most of the time, you’ll use the multiton service model with parameterized services, but you don’t have to. You could also use any of the prototype models as well.
  2. The constructor block for the :printer service takes three parameters, c, p, and name. The first two parameters, c and p, represent the container and the service point, respectively. Any parameters after those two are the contextual parameters given when the service is requested. In this case, there is only one contextual parameter: the name of the printer.
  3. Notice the first parameter to the Printer constructor: c.log_for(p). This is itself invoking a parameterized service, named :log_for, and passing p as the contextual information. This will return a new logger handle for the service point p (i.e., the current service point).
  4. See how the printer service is requested on the last two lines. In this case, the #printer message is sent to the registry with a single parameter. You can also request the service in two other ways:
Accessing parameterized services [ruby]
1
2
dot_matrix = registry[ :printer, :dot_matrix ]
ink_jet    = registry.get( :printer, :ink_jet )

Choose the style that works best for you.

2.4. Namespaces

Namespaces allow you to organize your services. The feature has many different applications, including:

  1. Third-parties may distribute Needle-enabled libraries without worrying about their choice of service names conflicting with the service names of their clients.
  2. Developers may organize complex applications into modules, and the service definitions may be stored in the registry to reflect that organization.
  3. Services deeper in the hierarchy may override services higher up.

Creating a namespace is as easy as invoking the #namespace method of the registry (or of another namespace):

Creating a namespace [ruby]
registry.namespace :stuff

This would create a new namespace in the registry called :stuff. The application may then proceed to register services inside that namespace:

Registering services in a namespace [ruby]
1
2
3
registry.stuff.register( :foo ) { Bar.new }
...
svc = registry.stuff.foo

Here’s a tip: namespaces are just a special kind of service. This means that you can access namespaces in the same ways that you can access services:

Accessing a namespace [ruby]
svc = registry[:stuff][:foo]

Convenience Methods

Because it is often the case that you will be creating a namespace and then immediately registering services on it, you can pass a block to namespace. The block will receive a reference to the new namespace:

More registering services in a namespace [ruby]
1
2
3
4
registry.namespace :stuff do |spc|
  spc.register( :foo ) { Bar.new }
  ...
end

If you prefer the define approach to registering services, you may like namespace_define, which creates the new namespace and immediately calls define on it:

Creating namespaces with #namespace_define [ruby]
1
2
3
4
registry.namespace_define :stuff do |b|
  b.foo { Bar.new }
  ...
end

And, to mirror the namespace_define method, there is also a namespace_define! method. This method creates a new namespace and then does a define! call on that namespace.

Creating namespaces with #namespace_define! [ruby]
1
2
3
4
registry.namespace_define! :stuff do
  foo { Bar.new }
  ...
end

The above code would create a new namespace called :stuff in the registry, and would then proceed to register a service called :foo in the new namespace.