6. Service Models

6.1. Overview

Service models are the mechanism by which a client can specify how the lifestyle of a particular service should be managed. By default, all services are managed as singletons, but it is a simple matter to choose a different behavior when a service is registered.

Underneath, service models are implemented using an instantiation pipeline.

6.2. Pipelines

An instantiation pipeline is a sequence of elements, each of which knows how to perform some aspect of the instantiation of a service.

Every service consists of at least one instantiation element—the block that was given when the service was registered. Other elements may be combined with this block to enforce various aspects of lifestyle management, such as multiplicity (singleton vs. prototype) and laziness (deferred vs. immediate instantiation).

Standard Pipeline Elements

There are six standard pipeline elements available in Needle (although you may certainly create your own):

  • deferred: this will always return a proxy that wraps subsequent pipeline elements, causing the subsequent elements to be executed only when a method is invoked on the proxy (at which point the method is then delegated to the resulting service).
  • initialize: this will invoke a method on the resulting service (defaults to initialize_service, though it can be changed). It is used for doing final initialization of services (for services that need it).
  • interceptor: this element is used to implement the proxy that wraps the interceptors around the service. It is only attached to the pipeline when an interceptor is attached to a service.
  • multiton: this element enforces a multiton guard on the service. This means that the service will only be instantiated once for each unique set of parameters given to the service.
  • singleton: this is a multiplicity guard that ensures a service is instantiated only once per process.
  • threaded: this is like the singleton element, but it ensures that a service is instantiated no more than once per thread.

Priorities

Just like interceptors, pipeline elements have priorities as well. These priorities determine the order in which the elements are executed in the pipeline.

Each element type has a default priority, although that priority can be overridden when the element is added to the pipeline.

Custom Pipeline Elements

Creating new pipeline elements simple. Just create a new class that extends Needle::Pipeline::Element. Set the default pipeline priority (using the #set_default_priority class method), and then implement the #call method (accepting at least two parameters: the container and the service point).

Custom pipeline element example [ruby]
1
2
3
4
5
6
7
8
9
10
11
12
require 'needle/pipeline/element'

class MyPipelineElement < Needle::Pipeline::Element
  set_default_priority 50
  
  def call( container, point, *args )
    ...
    result = succ.call( container, point, *args )
    ...
    return result
  end
end

To invoke the next element of the pipeline, just invoke #succ.call(...).

If needed, you can also implement #initialize_element (with no arguments), which you may invoke to perform initialization of the element. From there, you can access the options that were given to the element via the #options accessor.

See the implementations of the existing elements in needle/lifecycle for examples.

Added Pipelines Elements to Services

You can specify the pipeline elements to use for a service via the :pipeline option. This must refer to an array, each element of which must be either a symbol (in which case it references an element of the :pipeline_elements service), or a class (in which case it must implement the interface required by @Needle::Pipeline::Element).

Adding pipelines to services [ruby]
reg.register( :foo, :pipeline => [ :singleton, MyPipelineElement ] ) { ... }

The elements will be sorted based on their priorities, with lower priorities sorting closer to the instantiation block, and higher priorities sorting closer to the client.

Making Custom Pipeline Elements Available

You can make your custom pipeline elements available (so they can be referenced by symbol, instead of class name) by adding them to the :pipeline_elements service:

Publishing custom pipeline elements [ruby]
1
2
reg.pipeline_elements[ :my_pipeline_element ] = MyPipelineElement
reg.register( :foo, :pipeline => [ :singleton, :my_pipeline_element ] ) { ... }

6.3. Models

Specifying an entire pipeline for every service point can be tedious. For that reason, there are service models. A service model is a kind of template that names a pre-configured sequence of pipeline elements. Needle comes preconfigured with many service models.

Standard Service Models:

Name Pipeline Effect
:multiton :multiton The returned value will be unique for each unique parameter set given to the service.
:multiton_deferred :multiton, :deferred As :multiton, but a proxy is returned, deferring the instantiation of the service itself until a method is invoked on it.
:multiton_initialize :multiton, :initialize As :multiton, but invoke an initialization method on every service instance as soon as they are created.
:multiton_deferred_initialize :multiton, :deferred, :initialize As :multiton, but a proxy is returned, deferring the instantiation of the service itself until a method is invoked on it. When the service is instantiated, an initialization method will be invoked on it.
:prototype (empty) Immediately instantiate the service, at every request.
:prorotype_deferred :deferred Return a proxy, that will instantiate the service the first time a method is invoked on the proxy. A new proxy instance will be returned for each request.
:prototype_initialize :initialize Immediately instantiate the service, and invoke an initialization method, at every request.
:prototype_deferred_initialize :deferred, :initialize Return a proxy, that will instantiate the service and invoke an initialization method the first time a method is invoked on the proxy. A new proxy instance will be returned for each request.
:singleton :singleton Immediately instantiate the service the first time it is requested, returning a cached instance subsequently.
:singleton_deferred :singleton, :deferred Return a proxy that will instantiate the service the first time a method is requested on the proxy. Subsequent requests for this service will return the same proxy instance.
:singleton_initialize :singleton, :initialize Immediately instantiate a service and invoke an initialization method on it. Subsequent requests for this service will return a cached instance.
:singleton_deferred_initialize :singleton, :deferred, :initialize Return a proxy that will instantiate the service and invoke an initialization method on it the first time a method is requested on the proxy. Subsequent requests for this service will return the same proxy instance.
:threaded :threaded Immediately instantiate the service the first time it is requested from the current thread, returning a cached instance for every subsequent request from the same thread.
:threaded_deferred :threaded, :deferred Return a proxy object that will instantiate the service the first time a method is invoked on the proxy. Subsequent requests for this service from a given thread will return the thread’s cached instance.
:threaded_initialize :threaded, :initialize Immediately instantiate the service the first time it is requested from the current thread and invoke an initialization method on it. Subsequent requests for this service from the same thread will return the cached instance.
:threaded_deferred_initialize :threaded, :deferred, :initialize Return a proxy object that will instantiate the service and invoke an initialization method on it the first time a method is invoked on the proxy. Subsequent requests for this service from a given thread will return the thread’s cached instance.

Specifying a Service Model

You specify the service model by passing the :model option when you register a service. (You must only specify either the model, or the pipeline, but not both.)

Specifying a service model [ruby]
reg.register( :foo, :model => :singleton_deferred ) {...}

Defining New Models

You can create your own service models by adding the corresponding pipelines to the :service_models service:

Defining a custom model [ruby]
1
2
reg.service_models[ :my_service_model ] = [ :singleton, :my_pipeline_element ]
reg.register( :foo, :model => :my_service_model ) {...}