Copland Usage Reference

Registry

The Registry forms the core of the Copland framework. When you want a particular module or service, you will request it via the Registry.

Before the Registry can be used, however, it needs to be initialized. This simply involves calling the Registry#init method, but you may want to add some search paths to the Registry before doing so.

The search paths tell the Registry where to look for the module descriptor files (which are always named module.yml). These descriptor files will be discussed later. The only path searched by default is the Copland distribution directory, which contains the module descriptors for the standard services.

Once the Registry is initialized, you can query it to find modules and services. Here is an example:

  require 'rubygems'
  require_gem 'copland'

  registry = Copland::Registry.instance
  registry.add_search_paths( "my/conf/dir", "my/other/conf/dir" )
  registry.init

  # obtain a reference to a service by getting the module and then querying
  # the module.
  mod = registry.module( "my.app" )
  service = mod.service( "ServiceName" )

  # or, just query the registry directly
  service = registry.service( "my.app.ServiceName" )

  # then, the service is just an ordinary Ruby object. Invoke it's methods!
  service.call_this
  service.call_that

Module Descriptor Format

Module descriptor files are always named module.yml (which means you can only have one per directory; this helps encourage a hierarchical organization of modules). The files are in YAML format.

Here is a simple descriptor file, to demonstrate the basic layout:

  ---
  id: sample
  description: This is a sample module descriptor.

  service-points:

    SampleService:
      description: This is a sample service definition.
      create-instance:
        class: sample/SampleServiceImpl

Even if you are unfamiliar with the YAML text format, the above should be fairly readable. It says that the module id is ‘sample’, and provides a brief description of the module.

Then, the service points (the definitions of the services) are provided inside of a ‘service-points’ element. In this case, there is just one service, named ‘SampleService’, and it is given a brief description and the name of a class to instantiate. The ‘create-instance’ tag says how the class is to be instantiated; the simplest cases may use ‘create-instance’ (which simply instantiates the named class). More complex cases may use ‘invoke-factory’ (described later).

Note the format of the class name: all text up to the last ’/’ (forward slash) character are interpreted to be the name of a library to require, with the remaining text identifying the name of the class to instantiate. The require portion is optional. Then, when the class is about to be instantiated, the indicated library (if any) will be required first, and then the class instantiated.

Interceptors

Interceptors are objects that sit between a client and a service. When a client invokes a method on a service that has a registered interceptor, the interceptor will intercept the method call. This allows the interceptor to do additional processing (like logging, or enforcing security). The interceptor also has the option of invoking the service’s method, or of returning without calling the service.

Interceptor’s may be chained together, so that any given method invocation on a service transparently passes through an arbitrary number of interceptors. At each point in the chain, the interceptor may either pass control to the next interceptor in the chain, or abort the request by returning.

There is currently only one pre-defined interceptor in the Copland distribution, the LoggingInterceptor. It will emit log entries on method invocation and method return, and will also log uncaught exceptions.

Here is an example of how to define a service with an interceptor:

  SampleService:
    create-instance:
      class: sample/SampleServiceImpl
    interceptors:
    - service-id: copland.LoggingInterceptor

Developers may also define interceptors that accept parameters. The interceptor service itself must define a parameters-schema (discussed later) which describes the parameters it accepts. Then, those parameters may be specified when the interceptor is attached to a service, like this:

  SampleService:
    create-instance:
      class: sample/SampleServiceImpl
    interceptors:
    - service-id: copland.LoggingInterceptor
      file: /var/logs/sample-service.log
      threshold: info

In this example, the LoggingInterceptor is created, and (assuming it accepted two parameters, ‘file’ and ‘threshold’, which it doesn’t really) would initialize it with the given file name and logging threshold level.

Multiple interceptors may be specified for a single service by listing them under the ‘interceptors’ element:

  SampleService:
    create-instance:
      class: sample/SampleServiceImpl
    interceptors:
    - service-id: copland.LoggingInterceptor
    - service-id: my.app.SecurityInterceptor

By default, the interceptors will be added to the interceptor chain in the order they are specified, with the last interceptor listed being the first one to be invoked on any method call. If you want to change the order, you can specify the ‘order’ parameter to the interceptor, and the interceptors will then be sorted by that order. Lower orders will be placed lower in the chain (ie, closer to the service itself), while higher orders will be placed higher in the chain (ie, closer to the client).

  SampleService:
    create-instance:
      class: sample/SampleServiceImpl
    interceptors:
    - { service-id: copland.LoggingInterceptor, order: 1000 }
    - { service-id: my.app.SecurityInterceptor, order: 100 }

Builder Factories

The ‘create-instance’ element is sufficient for instantiating simple services, which need to external configuration and can completely initialize themselves. These kinds of services tend to be factories of one kind or another.

For many other services, it is more useful to be able to specify arguments to the constructor, and properties to set. This is where IoC containers really shine, because you can specify service-id’s as constructor arguments or properties, and the container will automatically grab an instance of that service for you. You simply compose and describe the relationships between objects, and the container wires them all together for you.

To facilitate these more complex instantiations, Copland has borrowed the concept of instantiation factories from Hivemind. Instead of using the ‘create-instance’ element, you use the ‘invoke-factory’ element, and then specify the service-id of the instantiation factory you wish to use. That factory will then be used to build the instance of the service you are defining.

These factory services also allow you to provide parameters to the factory, which it will use when determining how to construct the service.

Copland comes with one instantiation factory by default, the BuilderFactory service. Here’s a simple example that uses it:

  ComplexSample:
    invoke-factory:
      service-id: copland.BuilderFactory
      construct:
        class: sample/SampleServiceImpl
        parameters:
        - one
        - two
        - !!service-id my.other.module.AnotherService
        properties:
          something: value-of-something
          logger: !!log ~

In this case, the BuilderFactory takes one parameter, called ‘construct’. The construct parameter specifies the name of the class to instantiate, the list of constructor parameters, and the map of property values to set.

In particular, this demonstrates two special values that the BuilderFactory recognizes. The third constructor parameter looks like this:

  - !!service-id my.other.module.AnotherService

The !!service-id that prefixes the value tells the BuilderFactory to interpret the value as a service-id. The BuilderFactory will then look up that service and pass that as the value of the third parameter. Without the !!service-id prefix, the value would have been interpreted as a string, instead of a service.

The other value type is the second property:

  logger: !!log ~

In this case, the !!log type tells the BuilderFactory to create a new logger object (using the id of the service that is being constructed). The object will then be assigned to the ‘logger’ property of the new service. The tilde ’~’ at the end of the line represents ‘nil’. You could put anything you want there, but since the !!log value type ignores the actual value, it doesn’t really matter.

It should also be said that if a property is specified that doesn’t exist in the service, the BuilderFactory will dynamically add a setter and an instance variable to the object and will then use that setter to assign the property’s value to the instance variable.

Parameter Schemas

Under construction.

Service Models

Each service has an associated service model. This model determines when and how the service is actually instantiated. There are six default service models in Copland:

simple:The simple service model is the most basic model available. Any service that uses this model will be instantiated every time the service is requested. This makes it a useful model for a service that you want to be a factory—every lookup of the service gives you a brand-new object.
simple-deferred:This is just like the simple model, but the service won’t actually be instantiated until a method is invoked. The object you get back from looking up the service is actually a proxy, so if you never actually invoke any methods of the service, the service will never be instantiated.
singleton:This is like the simple model, except only one instance of the service is ever created. After the first one, all subsequent requests for this service return the same object.
singleton-deferred:This is a cross between simple-deferred and singleton. Only one instance of the service is ever created, but it is never created until a method is actually invoked on the service.
threaded:Like singleton-deferred, except each thread has its own copy of the service.
pooled:Like threaded, except that when a thread terminates, it’s copy of the service is returned to a pool for later requests to use.

The default service model is singleton-deferred. You can change which service model a service uses by specifying the ‘model’ element in its definition:

  MySimpleService:
    model: simple
    create-instance:
      class: simple-service/MySimpleServiceImpl

Creating Custom Service Models

You can create your own service models by inheriting from the Copland::ServiceModel class. (This makes the class a Singleton, and gives access to the register_as method for convenient registration of new service models.)

Once you’ve inherited from Copland::ServiceModel, just implement the Copland::ServiceModel#instantiate method (which accepts the service-point definition as the parameter, and returns the service instantiation).

(Here’s a tip: to instantiate a service point, just call it’s instantiate method, with no parameters.)

Then, all that’s left is to register the service model. Just call its register_as method, passing the name you want the model to be called.

See the implementations of the various service models in Copland for examples of how to write your own.

Examples

See the examples under the demo directory of the Copland distribution for help in getting started.