3. Getting Started
3.1. Terminology
In the tutorials and examples that follow, you’ll see a lot of (potentially) new terminology. Here is a brief glossary of some of the more prominant terms, to help you get started. Don’t feel like you need to read and understand each term before proceeding through this manual; just know that you can refer back to these if you need to.
- configuration point: This is either a list or a map (configurable) which packages may contribute to, and which services may read from to initialize themselves. Packages may define multiple configuration points.
- contribution: This is how a package contributes to a configuration point. A package may have any number of contributions, to any number of configuration points. Contributions may be made to configuration points both inside and outside of the current package.
- dependency injection: A design pattern in which the dependencies of an object are managed independently of the object, usually by some “container” framework. See “inversion of control”.
- event producer: A service that other services may listen to in order to obtain notifications of various service-specific events.
- interceptor: An interceptor is a special object that is attached to a service. A chain of interceptors may wrap the methods of a service and act as filters for any invocations of those methods. This is the means by which AOP-like “pre” and “post” hooks are implemented in Copland.
- inversion of control: A general design pattern in which various types of control are delegated (“inverted”) to some enclosing framework, called a “container”, “Dependency injection” is one form of control inversion, though the two terms are sometimes used interchangeably.
- listener: A service that has been registered with another service point in order to receive events produced by the latter service.
- package: The unit of organization in Copland. All service points, configuration points, and contributions are grouped into packages. Each package is defined in its own package descriptor.
- package descriptor: The file that contains the definition of single package. This always called
package.yml
and contains YAML. - multicast service: A special kind of service that, by itself does nothing, but which other services may register themselves with. Then, when a method on the multicast service is invoked, the corresponding methods of all registered services will be invoked.
- registry: This is the primary interface into Copland, used by clients. It is the broker that returns services requested by clients.
- service: This is the basic unit of activity in Copland. The service model determines how the service is instantiated, such as whether it is a singleton or not. Some services are actually service factories which may in turn be used to help create more complex services. Another special type of service is the interceptor factory, which is used to create interceptor instances. An application’s components will typically be implemented as services.
- service factory: A service factory is a special kind of service, which may be used to bootstrap the creation of more complex services. Copland comes with several predefined service factories, most notably the “BuilderFactory” (which may be used to create the vast majority of the services you will need).
- service model: A service model defines how and when a service is instantiated. There are six predefined service models, allowing for simple instantiation (one instance per request, created at the time of the request), singleton instantiation (only one instance of the service is ever created, and it will be returned every time the service is requested), deferred instantiation (the service will be created the first time a method is invoked on it), and even combinations of these. The default model is “singleton deferred”.
- service point: A definition of a service. A service point is to a service as a class is to an object. The service point contains the configuration of interceptors, event producers, service model, service factory, and so forth that will be used to construct its corresponding service.
3.2. Quickstart
This “Quickstart” is designed to show you the “look” of a simple Copland application. It is not intended to teach you how to use Copland, nor to be an example of application design in Copland. Very little time will be spent on explanation. If you wish to actually learn how to use Copland, refer instead to the Tutorials section, and the Examples section.
Service Implementation
A service is typically backed by a simple, ordinary Ruby object, instantiated from a class. You don’t have to do anything special to your definition of the class (unless you want it to interact with other services—but that’s beyond the scope of this “quickstart”).
class ASimpleService def do_something ... end def do_something_complicated( a, b, opts={}, &block ) ... end end
For the rest of this example, we’ll assume the above implementation is contained in a file called simple-service.rb
.
Service Definition
The services are always defined in a package descriptor file. This file is always named package.yml
, which means you can only have one per directory. It is a YAML-formatted description of a package, which will include all of the service points (service definitions) for that package.
--- id: quickstart service-points: SimpleService: implementor: simple-service/ASimpleService
Registry Initialization
Creating a registry is simple: just invoke build
on the Registry class.
require 'copland' registry = Copland::Registry.build
The build
method is a convenience method. It takes care of searching subdirectories for package descriptors and adding them to the new registry.
Getting Services
Services are queried through the registry. The name of a service is the name of it’s package (the ‘id’ in the package descriptor), followed by a dot ’.’, followed by the id of the service.
Once you have a reference to the service, you can invoke its methods just as if it were a plain-old Ruby object.
service = registry.service( "quickstart.SimpleService" ) service.do_something service.do_something_complex( 1, 2, :name=>"Jim" ) { |a| puts "#{a}: here" }
Service Initialization
Sometimes (especially for “prototype” services—see the prototype service model for more info) you want to be able to pass some kind of initialization data to the new service the first time it is initialized. You could do this with a method that you explicitly call after obtaining a reference to the service—but then the service needs to keep track of whether the method has already been invoked or not, to prevent the initialization from occurring more than once per service instance.
To make this simpler, Copland 0.8 introduced the concept of an initialization block. When you call Registry#service
, you can attach a block to it, and that block will be passed to instance_eval
on the new service every time that service is instantiated.
Something like this:
service = registry.service( "quickstart.SimpleService" ) do ... # put your initialization code here, to be executed every # time this service is instantiated. ... end
If the service was instantiated previously (i.e., it is a singleton service and was requested earlier), then the initialization block will be ignored.