11. Service Factories

Sometimes it requires a little more effort (or a lot more effort) to instantiate and initialize a service than simply calling #new on it’s associated class. In such cases, you need to rely on a service factory to instantiate the service.

A service factory is just a regular service as far as Copland is concerned. It is declared in the usual way. However, a service that will be used as a service factory must implement at least one method: #create_instance. This method should accept two parameters, the service point to instantiate, and the parameters associated with this instantiation. (The parameters parameter will always be a hash.)

Here is an example that implements a trivial service factory:

  class ExampleServiceFactory

    def create_instance( point, parms )
      return { :point => point,
               :parms => parms }
    end

  end

This service would be introduced into Copland via the following package descriptor:

  ---
  id: example

  service-points:

    ExampleServiceFactory:
      implementor: some/file/ExampleServiceFactory

And it would be employed like this:

  ---
  id: demo

  service-points:

    ExampleService:
      implementor:
        factory: example.ExampleServiceFactory

This service factory, when used to instantiate a service, would always return a new Hash object consisting of the service point and its parameters. Thus, the ExampleService service point would, when instantiated, always consist of a hash containing its own service point, and any parameters that were given when it was instantiated (none, in this case). Not a very useful service factory, but it demonstrates what it ought to do.

11.1. Schemas

As mentioned in the chapter on service points, a service point may be associated with a schema. More will be said on schemas (and specifically, on their formats) in the next chapter, but suffice it to say here that the schema allows a service point to specify what parameters it accepts when invoked as a factory service.

11.2. How do they work?

When you specify a factory service as the implementor of another service, Copland automatically marks that service point as needing a complex instantiator. Thus, when it comes time to instantiate the service point, the parameters are collected, and if the factory has a schema, the parameters are validated against that schema. Then, the parameters are preprocessed (to translate values to their appropriate and expected types), and the factory’s #create_instance method is called. The result of that call is then treated as the new service.

Contrast this with the simple instantiator. When the simple instantiator is used, all it does (more or less) is invoke #new on the named class and return the result. The existence of factory services allows for much more complex (and powerful) behavior.

11.3. BuilderFactory

Copland comes with one predefined factory service: copland.BuilderFactory. With this factory you can implement most of the more common types of services. (There are always special cases, though—the copland.lib, copland.remote and copland.webrick libraries all define additional factory services for specialized uses.)

The BuilderFactory allows you to not only instantiate a class, but to specify constructor parameters and set properties on the new object. It also allows you to specify methods that should be invoked in order to initialize a service. It is by this means that the “dependency injection” aspect of Copland comes into play.