Copland IoC Container
Copland is an Inversion of Control (IoC) container, in the same vein as Hivemind, Spring, and PicoContainer (in the Java universe). It borrows concepts, terminology, and even some architecture from Hivemind, with the exception that it uses YAML for the module descriptors instead of XML.
Why Should You Care?
First, it should be stated that IoC will not magically allow you to write programs that would not have been possible without it. However, it does allow you write programs that would have been much more complex without it.
There is a good use case on the Hivemind project: jakarta.apache.org/commons/sandbox/hivemind/case1.html. It is, however, unfortunately littered with large snippets of Java code and XML. If you can read around those lengthy examples, the use case itself is very compelling.
In general, very large (perhaps enterprise-level) architectures can nearly always benefit from IoC. It drastically reduces the coupling between components, and allows for new components to be easily swapped in and out, or substituted for other components. Interceptors allow you to seamlessly use AOP concepts on your services, non-invasively and unobtrusively adding code to be executed before and after any method call. Listeners allow you to invoke a service’s methods when another service is created or pooled, or when the registry is first initialized. And modules allow you to group your services and configurations by common functionality.
On the other hand, smaller applications may find IoC of limited usefulness. The added complexity will often be unnecessary for them, and the overhead added to method calls inside the container will rarely be justified.
IoC is like any other tool. It’s not a panacea, but when used appropriately, it can greatly increase the maintainability and extensibility of large systems.
Downloading
You may download Copland from Copland’s RubyForge project, at rubyforge.org/projects/copland. Copland is distributed both as a RubyGem, and as a Ruby library.
Installation
Copland is distributed as a gem, which makes it extremely easy to download, install, and use. However, it requires that you have RubyGems installed, first.
Also, since Copland relies on the Log4r library, you’ll need to download and install it first.
Assuming you have RubyGems installed, all you have to do is:
gem --remote-install copland
This will install the latest version of Copland.
Copland is also distributed as a Ruby library. It’s not as easy to install this way, but it works. Simply grab the latest copland package from the website, untar it, and install it by typing:
ruby setup.rb config ruby setup.rb setup ruby setup.rb install
Dependencies
Copland has the following dependencies on other libraries (all freely available from the RAA):
- Log4r: Copland uses Log4r for logging, and as such it is tightly integrated with Copland in the form of Copland::LoggingInterceptor.
- YAML: YAML is used for the module descriptor files. If you are using Ruby 1.8.x, then YAML is installed by default.
Inversion of Control
"Inversion of Control" (IoC) is a fancy term that, frankly, doesn’t mean much. Anyone that has written software has used it to some degree or another, whether they’ve realized it or not. It’s really just a design pattern that refers to the strategy of delegating "control" to some other object. Factory classes are a form of inversion of control, since the program has delegated the act of instantiating objects to some other object.
Inversion of Control containers represent a special case of IoC—one that has been referred to as Dependency Injection (which, frankly, is just another design pattern).
Dependency Injection in Action
Consider the following simple example. You‘ve written an application that presents a database of books to the user. For your first pass, you have it simply read the "database" from a YAML file. You want restrict certain operations on the database so that only certain users can access it. You also want to be able to log all access to the database so you can identify who did what, when. Lastly, you want the output to be customizable by the user, so you provide a templating system.
What are the objects in this system?
- Application.
- Database.
- Authentication
- Authorization
- Logging
- Templating
Writing this using traditional methods, you would have the Application instantiate and configure the Database, Authentication, and Templating systems. The Database would then instantiate and configure the Authorization and Logging systems. Each instantiation/configuration could potentially take a lot of code (set up constructor parameters, instantiate the object, assign properties, etc.). But even if each one were a simple one-line "SomeSystem.new" call, that still means you have to explicitly instantiate each class and wire it somehow to its dependencies:
class Application
def initialize
@db = Database.new
@auth = Authentication.new
@template = Templating.new
end
end
class Database
def initialize
@auth = Authorization.new
@log = Logging.new
end
def something( user )
@log.write( "entered something as #{user}" )
begin
@auth.check( :something, user )
...
rescue Exception
@log.write( "exception raised in something" )
raise
end
@log.write( "exited something" )
end
end
app = Application.new
Using dependency injection (as implemented in Copland), all of the object instantiation and configuration is moved from the code to a configuration file. In the configuration file, you would specify that the Application has as dependencies Database, Authentication, and Templating. You would also specify that Database depends on Authorization and Logging.
Then, in your application, you would query the container for the Application (instead of instantiating it directly), and the container would take care of instantiating and configuring all of its dependencies, and assigning them to the appropriate properties of the Application.
Another slick feature of IoC containers is this: because all of your actual class instantiation occurs in the container via the configuration, you can change which class to use simply by chaning the configuration. Thus, if you decide you want to use a MySQL database instead of a YAML file for the application data, you can simply specify a class that uses MySQL instead of YAML. None of your code has to change!
Lastly, consider the "something" method of Database, above. A log entry is written on entrace to the method, the authorization is checked for the given user, and then a log entry is written on method exit. A log entry will also be made if an exception is raised. Every method of Database would need this code in order to consistently check authorizations and log entry and exit.
Copland can make this much easier by using interceptors. You would create a logging interceptor and a security interceptor and then wire those interceptors into the Database service. Then, every method that gets invoked on the Database would first pass through the interceptors, which could do logging and authorization checking transparently. Your code remains clean and easy to read!
Summary
If you installed Copland as a gem, simply put the following two statements at the top of your programs:
require 'rubygems' require_gem 'copland'
If you installed Copland as a Ruby library, use the following require statement:
require 'copland'
For information on how to use Copland, refer to the files/doc/USAGE.html document.
Tutorials
If you are interested in learning how to use Copland, you may find the tutorials valuable: files/tutorial/tutorials_rdoc.html.
Credits
Thanks go to:
- Matz, for creating Ruby in the first place.
- Howard Lewis Ship, for the Hivemind Java framework that inspired Copland.
License
Copland is copyright © 2004 Jamis Buck. It is free software, and may be redistributed under the terms specified in the README file of the Ruby distribution.
Future Directions
See the files/doc/TODO.html document.