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 toinitialize_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 thesingleton
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).
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).
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:
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.)
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:
1 2 | reg.service_models[ :my_service_model ] = [ :singleton, :my_pipeline_element ] reg.register( :foo, :model => :my_service_model ) {...} |