1. Introduction
1.1. What is Copland?
Copland is an Inversion of Control (IoC) container, written in Ruby, for use in Ruby programs. In a nutshell, it allows you to simplify the instantiation and initialization of your classes.
1.2. What Can Copland Do For Me?
So, what can Copland do for you? Ultimately, it can reduce the amount of code that you have to write, simplifying many common programming tasks for you. This has the two-fold benefit of both decreasing application development time, and of decreasing the effort needed to maintain your application.
But what, specifically, can Copland do for you?
Try these on for size:
- Log Method Execution
- Reference Another Service
- Service Configuration
- Unit Testing
- Lifecycle Management
(Thanks to Howard Lewis Ship for his HiveMind documentation, from which most of the above bullet points were adapted.)
A. Log Method Execution
Copland has an integrated logging framework, and the ability to log execution trace information without modifying a single line of your code. This means that you can easily see what methods get called, with what arguments, and what the return values are, all without having to add a single line of logging code to your classes.
Consider the following code, demonstrating how this would be done without Copland:
def foo( arg1, arg2 ) @log.debug( "in foo with #{arg1} and #{arg2}" ) if @log.debug? ... result = the_result_of_the_method @log.debug( "finishing foo with #{result}" ) if @log.debug return result rescue Exception => e @log.debug( "foo raised exception #{e.message} (#{e.class})" ) if @log.debug? raise end
Now, multiply that by the number of methods in your class… the logging messages quickly overpower the rest of the code, and detract from the flow of your program. This makes your program harder to debug, test, and maintain.
Now, consider the same method using Copland’s integrated logging framework…
def foo( arg1, arg2 ) ... return the_result_of_the_method end
Uh, huh. That’s right. There’s no explicit logging code in there. Instead, you just tell Copland that the methods of the class should be logged, and away it goes. This has the added benefit of allowing your objects to be unit tested, without spewing log messages everywhere. (Look for the tutorial about “Logging Interceptors” for an actual demonstration of how to do this.)
B. Reference Another Service
Invariably in a large application services will reference other services. This is typically accomplished through something like this:
def foo( parms ) @service ||= lookup_service @service.do_something( parms ) end
Whether the lookup is done lazily, as shown above, or when the class is first instantiated is irrelevant. The point is that you either have to implement a bunch of code to look up a service based on some criteria, or you hard code the class of the service (which creates tight coupling and makes things like unit testing harder).
With Copland, you just declare a setter for the service, and then tell Copland that the class depends on the other service:
attr_writer :service def foo( parms ) @service.do_something( parms ) end
Then, when your service is instantiated, Copland will automatically look for, instantiate, and set the dependencies for you. This makes for cleaner code, and looser coupling between services.
C. Service Configuration
Often, a large application or library will need some way to allow different classes to be configured at runtime. Whether you have a factory class that requires the available producable classes to register themselves with it, or whether you just want to allow third-parties to be able to extend your application, you wind up implementing some form of decentralized configuration.
Copland allows you to define configuration points, which any package may then contribute values to. Furthermore, just as services can reference other services, services can reference configuration points, and Copland will manage them just like any other dependency.
See the tutorial about “Configuration Points” for an example of this feature.
D. Unit Testing
Large applications can prove troublesome to unit test exhaustively, especially if there is any kind of tight coupling between components. Such coupling of components can make it difficult to test them separately.
Copland, by its very nature, encourages loose coupling of components. Also, because dependencies are never instantiated in code, but are instead accepted via setters or constructor arguments, it is trivial to replace those dependencies with mock objects at unit test time.
Consider this tightly coupled example:
def foo( args ) @some_dependency ||= MyNewDependency.new @some_dependency.do_something(args) end
It is impossible to test the method #foo
without also testing the MyNewDependency class. However, if the @some_dependency
object is made a property that is set externally, you can replace it at test time with a blank:
attr_writer :some_dependency def foo( args ) @some_dependency.do_something( args ) end
The unit test would become something like this:
def test_foo @obj.some_dependecy = MyMockDependency.new @obj.foo( args ) assert @obj.is_in_some_state end
E. Lifecycle Management
Singleton objects are a fact of life in complex systems. The singleton design pattern is powerful and useful. However, using the Singleton mixin, or declaring methods at the class level, can make your code difficult to unit test since the state of such objects cannot be easily reset.
Copland has a solution. You can tell Copland to treat a service as either a prototype service (meaning it will be instantiated every time you ask for it, like calling #new
), or a singleton service (meaning it will only be instantiated once, and the same instance will be returned for subsequent requests).
Your object is still just a plain ol’ ordinary Ruby object, but Copland has effectively transformed it into a singleton. This means you can unit test it as if it were nothing special, but when it is used in your application it will act like a singleton.
Lifecycle management also means that you can control when a service is instantiated. The prototype and singleton models will always be instantiated as soon as they are requested. Sometimes, though, you don’t want that—you’d like the instantiation to be deferred as long as possible.
With Copland, you can indicate that a service should use deferred instantiation. This will cause the service to not actually be instantiated until a method is actually invoked on it. Using this model, you can have services depend on themselves, or other forms of cyclical dependencies.
1.3. Copland sans Buzzwords
Imagine being able to take all the various pieces of your program and implement them in a vacuum. You just assume that all dependencies will be satisfied at runtime, by properties being set, or parameters being passed to each object’s constructor. Nowhere in your code do you specify the instantiation of another application component.
However, those dependencies and relationships between objects still exist; you’ve just written your code without any explicit knowledge of them. Somehow, you still need to be able to wire the pieces all together so they work as required in tandem. To put it another way, you need to be able to take your classes and compose them into something greater.
Enter Copland.
Copland helps you tie the pieces of your application together. Instead of inter-object relationships and dependencies being specified in the code, you feed them into Copland. This makes those relationships easier to modify, since you never have to modify the source code to change them.
Copland is named after the American composer Aaron Copland for a reason. You may or may not have any musical talent, but Copland will give you the ability to compose great “symphonies of software” by allowing you to work easily at both extremes of software architecture:
- It helps you to focus on the implementation of a single class by allowing you to ignore the implementation details of the dependencies of the current class.
- It helps you to focus on the relationships between classes by allowing you to specify those relationships and dependencies as run-time configuration options.
1.4. Copland avec Buzzwords
If you are already familiar with IoC, then you either love it or hate it. In the first case, you probably only want to know what features Copland has compared to other IoC containers you’ve used. In the second case, there’s probably not much I can say to change your opinion, so I won’t try.
I’ll save all the meaty discussions for the chapter on Copland’s design, but here’s the rundown:
- Copland is based heavily on the HiveMind IoC container for Java. If you’ve ever used that one, much of Copland will probably feel very familiar to you.
- Copland allows you to define service points, configuration points, contribute to configuration points, add interceptors to services, add listeners to services, define multicast services, and even make your services web-enabled by using the built-in SOAP or dRuby interfaces.
- Copland uses YAML for its configuration files, instead of XML. If you’ve never used YAML, you’ll find it surprisingly easy to pick up.
1.5. The Buzzwords Themselves
As Martin Fowler points out, the term “Inversion of Control” is a poorly-chosen one. Still, it is the one that most people know the concept by, so I’ll use it throughout this document.
For more info about IoC, you might try reading the following articles:
- Inversion of Control Containers and the Dependency Injection pattern (Martin Fowler)
- Inversion of Control (from the PicoContainer site, another Java-based IoC container)
- Inversion of Control (HiveMind’s take on the subject)
1.6. Features
Currently, Copland features the following buzzwords:
- Type 2 IoC (setter injection)
- Type 3 IoC (constructor injection)
- YAML-based descriptor configuration
- Object-, Class-, and Singleton-backed services
- Service factories, with extendable rule-processing engine for defining new factories.
- Interceptors (for adding basic AOP-like “pre” and “post” hooks to every method of a service)
- Multicast services, which do nothing themselves except delegate recieved messages to a set of other interested services.
- Event/Listener infrastructure, for notifying services at various points during another service’s lifecycle.
- dRuby and SOAP support, in the form of two core services that applications may use to export their services to remote clients.
1.7. Getting Copland
Using RubyGems
Installing via the RubyGems system is trivial—you just need to make sure that you have RubyGems properly installed. Once RubyGems is installed, make sure you are logged in as a user with permissions to update your local gem repository, and then type:
gem install copland
Alternatively, you can download the gem file itself (if, for instance, the RubyGems gem repository is down, or hasn’t been updated with the latest version of Copland). Just go to Copland’s RubyForge project page and download the gem for the latest version. Then, from the command-line, type:
gem install path/to/the/file/you/downloaded.gem --local
Manual Installation
If, for whatever reason, RubyGems is not an option for you, you can go to Copland’s RubyForge project page and download the tar.gz
file for the latest version of Copland. Then:
- Unpack the archive. It will extract all files to a new directory under the current directory.
cd
to the new directory.- Make sure you are logged in as a user with permissions to install Ruby libraries. If you are, just type:
ruby setup.rb config ruby setup.rb setup ruby setup.rb install
1.8. License Information
Copland is currently distrubuted under the BSD license. Versions of Copland prior to 0.6 were released under the same license as Ruby.
This manual (both in YAML and HTML formats), as well as the minimal Ruby scripts used to generate it, are distributed under the Creative Commons Attribution-ShareAlike license.
1.9. Support
Mailing lists, bug trackers, feature requests, and public forums are available (courtesy of RubyForge) at Copland’s RubyForge project page. Just direct your browser to http://rubyforge.org/projects/copland.