Channels ▼
RSS

Web Development

Inversion of Control In Perl


Stevan is the Senior Developer at Infinity Interactive, a small NYC consultancy specializing in building LAMP applications for a number of corporate clients. Stevan can be contacted at stevan@ iinteractive.com.


Inversion of Control (IoC) is the very simple idea of releasing control of some part of your application over to some other part of your application or an outside framework.

IoC is a common paradigm in GUI frameworks, whereby you give up control of your application flow to the framework and install your code at callback hooks within the framework. For example, take a very simple command-line interface: The application asks a question, the user responds, the application processes the answer and asks another question, and so on until it is done. Now consider the GUI approach for the same application: The application displays a screen and goes into an event loop, users actions are processed with event handlers and callback functions. The GUI framework has inverted the control of the application flow and relieved your code of having to deal with it.

If you hang around enough Java programmers, chances are you have heard the phrase "inversion of control" thrown around recently. IoC is also sometimes referred to as "dependency injection" or the "dependency injection principle," and many people confuse the two. IoC and dependency injection are not the same, and in fact, the concepts behind dependency injection are actually just an example of IoC principles in action (in particular, as they relate to your application's dependency relationships). IoC is also sometimes referred to as the "hollywood principle" because of the "don't call us, we'll call you" approach of things like callback functions and event handlers.

Despite its current overhyped buzzword status, IoC is nothing new. It is a time-tested concept that most programmers use and encounter on a daily basis. And while the ideas behind the current dependency injection hype have been around for several years, they are now reaching a level of maturity where their widespread use is becoming practical.

Howard Lewis Ship, the creator of the HiveMind IoC Framework, once referred to dependency injection as being the inverse of garbage collection. With garbage collection, you hand over the details of the destruction of your objects to the garbage collector. With dependency injection, you are handing over control of object creation, which also includes the satisfaction of your dependency relationships. In his opinion, just as many programmers today would never want to go back to manual memory management—soon programmers will forget the days when they had to manage the creation of their own objects.

This is just the tip of the IoC iceberg, though. If you expand the idea of managed object creation, it becomes clear that it can be used for much more than just creating objects and handling dependency relationships. It becomes much simpler to do a number of things that would otherwise complicate your design; for instance, wrapping your objects in AOP-like method call tracers/proxies, silently substituting mock objects for real ones during testing, or exerting transparent and fine-grained control over the lifecycle of your objects. But all this is heavily in the abstract, so let's get down to something more concrete.

IOC in Action

I recently released an IoC framework to CPAN called IOC, which is available at http://search.cpan.org/~stevan/IOC/. It is still a work in progress, and its current focus is on object creation and managing dependency relationships. Future directions will include more work with object proxies and AOP-style technologies. The following is a discussion of how you can use IOC in its current form.

Containers

The central part of just about any IoC framework is the container. A container's responsibilities are roughly to dispense objects, to handle the resolution of said object's dependency relationships, and to manage the lifecycles of the objects.

Let's start with dispensing objects. First, we must create a container for our objects to live in and give it a name:

my $c = IOC::Container->new('MyApplication');

Next, we need to add a component to that container and give it a name. In the IOC framework, we use an IOC::Service to wrap and manage the lifecycle of our components:

$c->register(IOC::Service->new('logger' 
        => sub { My::Logger->new() }));

Now, if we want an instance of our logger component, we simply ask the container for it:

my $logger = $c->get('logger');

Pretty simple.

Dependency Management

Dependency management is also quite simple and is easily shown with an example. But first, let's create another component for our container—a database connection:

$c->register(IOC::Service->new(
  'db_conn' => sub { 
    DBI->connect('dbi:mysql:test', '', '') 
  }
));

Now, let's add an authenticator to our container. The authenticator requires both a database connection and a logger instance in its constructor:

$c->register(IOC::Service->new(
  'authenticator' => sub {
    my $c = shift;
    My::Authenticator->new(
      $c->get('db_conn'), $c->get('logger')
    );
  }
));

As you can see, the first argument to our service subroutine is actually our container instance. Through this, we can then resolve the authenticator's dependency relationships.

Lifecycle Management

The default lifecycle for IOC::Service components is that of a Singleton, which means each time we ask for, say, the logger, we will get the same instance back. There is also another option for lifecycle management that we call "prototype." It is worth noting that this is not the same as prototype-based OO and should not be confused with that. Here is an example of how we would use the prototype lifecycle:

$c->register(IOC::Service::Prototype->new(
  'db_conn' => sub { 
    DBI->connect('dbi:mysql:test', '', '')
  }
));

Now, each time we request a new database connection from our container, we will get a new one. Being able to change between the different lifecycles by simply changing the service wrapper will come in handy as your application grows. Extending this idea, it is possible to see how you could create your own custom service objects to manage your specific lifecycle needs, such as a pool of database connections.

Unit Testing with Mock Objects

We have now decoupled the creation of our authenticator from the creation of both our logger instance and our database connection. We have already seen that the respective lifecycles of each component are irrelevant to their sibling components and can be varied as needed. And since nothing is referred to directly, only by name through the container, we are free to change the details and/or implementation of our components and to do something like substitute mock components.

In the unit tests for our authenticator, we might want to use the mock DBI driver DBD::Mock. To do this, we would only have to change the details of our db_conn service and none of our other components would be the wiser:

$c->register(IOC::Service->new(
  'db_conn' => sub { 
    DBI->connect('dbi:Mock:mysql', '', '')
  }
));

We could also substitute our own mock logger—one that would check the expected log statements without needing to parse a log file. In abstracting away the details of the creation of an object, IoC makes the use of mock objects a much simpler process than it might otherwise be.

Debugging with Proxies

As we have seen with lifecycle management and switching in mock objects in our unit tests, we can do a number of things to a component behind the scenes and not have it affect our other components. The IOC framework also offers another means of doing this through its IOC::Proxy module.

IOC::Proxy implements a (mostly) transparent proxy package around a particular instance of a component. Through this proxy, we can actually capture each method call and do pretty much anything we like. The most obvious usage is for logging method calls for debugging purposes. Here is an example of a basic debugger proxy:

my $p = IOC::Proxy->new({
  on_wrap => sub {
    my ($p, $object_being_wrapped, 
             $proxy_package) = @_;
    warn(">>> wrapping $object_being_wrapped
                       with $proxy_package\n");
  }  
  on_method_call => sub {
    my ($p, $method_name, $full_method_name,
                         $method_args) = @_;
    warn(">>> $method_name called with [" . 
             (join ", " => @$method_args) . 
        "] dispatching to $full_method_name\n");
  }
});

In order for this to be useful, we need to install the proxy on a component. There are two ways of accomplishing this. Either we can associate our proxy with a component at registration time using the registerWithProxy method, like so:

$c->registerWithProxy(IOC::Service->new(
  'authenticator' => sub { ... }, $p
);

or we can add the proxy to a component by name, using the addProxy method:

$c->addProxy('authenticator', $p);

The result is that when you ask for our authenticator component, you will actually be given back a proxy object, which is almost indistinguishable from the real component. The proxy object actually does quite a lot to cover its tracks and will respond as expected to isa and can (including UNIVERSAL::isa and UNIVERSAL::can) and will not leave any information to be found in the output of caller. It will even handle overloaded operators and AUTOLOAD correctly. The only place in which the proxy object is easily detectable is when it is passed to ref.

This is just the base for the proxying possibilities. There are plans in the works for creating more specialized proxy objects and these will likely show up in future releases of IOC.

IOC in Detail

I have shown how to use IOC to manage components lifecycles, their dependencies, and even how the IOC framework itself can be used to help unit testing and debugging. All of this can be done with a fairly high degree of decoupling and only a small configuration overhead. For the most part, these represent the most common and basic usage of the IOC framework, but as your application grows, there are other, deeper parts of the IOC framework that might come in handy.

Service Creation/Injection Styles

So far, I have shown the default way of creating a service or component, using an anonymous subroutine. But this is not the only way to go about this. Those who have encountered IoC in the Java world may be familiar with the idea that there are three "types" of IoC/dependency injections: constructor injection, setter injection, and interface injection. In IOC, we support both constructor and setter injection. However, I decided that interface injection was not only too complex, but highly java-specific, and the concept did not adapt itself well to Perl. The three injections styles IOC supports are as follows.

Block injection. This is IOC's default style of injection. It is not one of the official three types (mostly because it's not possible in Java), although it can be found in a few Ruby IoC frameworks, and "Block injection" is my own term for it. It could be viewed as a compound injection style, in that it can be used to mix constructor and setter injection. Take this example:

my $s = IOC::Service->new('app' => sub {
  my $c = shift;
  my $app = My::Application->new($c->get('authenticator'));
  $app->setLogger($c->get('logger'));
  $app->setDatabaseConnection($c->get('db_conn'));
  return $app;
});

The My::Application object requires an authenticator in its constructor, and then a logger instance and a database connection are added through setter methods. This allows for a great deal of flexibility with component creation and especially tends to work best when retrofitting an application with IOC.

Constructor injection. With constructor injection, the container calls the object's constructor and feeds it the required arguments. This promotes what is called a "good citizen" object, or an object that is completely initialized upon construction. This is the style used and popularized by the Pico Container Java Framework. IOC supports this style through the IOC::Service::ConstructorInjection and IOC::Service::Prototype::ConstructorInjection modules. Here are some examples of how constructor injection is done in IOC:

my $s = IOC::Service::ConstructorInjection->new(
  'db_conn' => (
     'DBI', 'connect', [ 'dbi:mysql', 
                     'user', '****' ]
  )
);

Dependencies are managed somewhat differently with constructor injection than they are with the default block injection. Here is an example of how that looks:

my $s = IOC::Service::ConstructorInjection->new(
  'authenticator' => (
    'My::Authenticator', 'new', [
IOC::Service::ConstructorInjection
              ->ComponentParameter('db_conn'),
IOC::Service::ConstructorInjection
              ->ComponentParameter('logger'),
    ];
  )
);

As you can see, the class method ComponentParameter is used as a placeholder for any dependencies that need to be resolved at component creation time. There are advantages to this style, one of which is that it promotes the "good citizen" object pattern. However, it is easy to see how a constructor could get very messy as the number of dependencies are increased.

Setter Injection. IOC also supports setter injection. The idea behind setter injection is that for each component dependency, a corresponding setter method is created. This style has been popularized by the Spring Java Framework. IOC supports this style with its IOC::Service::SetterInjection and IOC::Service::Prototype::SetterInjection modules. Here is an example of how setter injection is done with IOC:

my $s = IOC::Service::SetterInjection->new(
  'app' => sub {
    'My::Application', 'new'	=> [
     { setAuthenticator	=> 'authenticator' },
     { setLogger		=> 'logger'        },
     { setDatabaseConnection	=> 'db_conn'       },
    ]
  }
);

As you can see, for each setter method, we provide a component name to be passed to it. This style has its advantages, but one obvious problem is the public nature of the setters.

Hierarchal Containers

So far, I have shown basic containers that only have a single level of components. As your application grows larger, it may become useful to have a more hierarchal approach to your containers. IOC::Container supports this behavior through its many subcontainer methods. Listing 1 is an example of how we might rearrange the previous examples using subcontainers.

We have introduced the IOC::Container method find in this example. This method can be used to find a component that is stored outside of the current components container and it supports a basic path-like syntax for climbing your container hierarchy.

Global Container Registry

IOC provides a global container registry through the module IOC::Registry. IOC::Registry is a Singleton and can be used to store and then access all of your containers and services; it also includes the ability to search for services and containers within your hierarchy. You would likely initialize your registry Singleton in the same configuration file where you created all your containers. So utilizing the code from Listing 1, your registry might look like this:

my $r = IOC::Registry->new();
$r->registerContainer($app_c);

Now, from anywhere else in your code, you could do something like this:

# get the singleton instance
my $r = IOC::Registry->instance(); 

# and now try to find a particular service
my $s = $r->searchForService('laundry') 
  || die "Could not find the laundry service";

# or for a particular container
my $s = $r->searchForService('database') 
  || die "Could not find the database container";

You can also address services and containers by paths, much like the find method of the IOC::Container object. That would look like this:

my $db_conn = $r->locateService('/database/connection');

The registry object can serve as a central point for all your object dispensing needs, and reduce a sometimes complex collection of Singletons and object factories.

The Future of IOC

IOC is still very much a work in progress. Its current version as of this writing is 0.11, but no doubt that will change as I am taking the "release early/release often" approach to this module. Here are some directions IOC may take:

  • Handling cyclical dependencies. Currently IOC does not handle cyclical dependencies. It can catch the cyclical dependency and will throw an exception when it is found. I am currently looking for a way to overcome this problem and hope to have it resolved soon.
  • XML or YAML configuration. I have been experimenting with both XML- and YAML-based configuration files, which could be used to automagically create containers.
  • Dependency visualization. As an application grows, it can become more and more difficult to visualize all the interdependencies within it. I can see a real use for having the ability for IOC to visualize those dependencies for you using some kind of graph drawing tool like GraphViz.
  • More proxies. The current IOC::Proxy object is just the beginning of what can be done using this AOP-like approach. I have a number of ideas for all sorts of funky proxy objects.

TPJ



Listing 1

my $app_c = IOC::Container->new('app');

my $db_c = IOC::Container->new('database');
$db_c->register(IOC::Service->new('dsn' => sub { 'dbi:mysql:test' }));
$db_c->register(IOC::Service->new('username' => sub { 'user' }));  
$db_c->register(IOC::Service->new('password' => sub { '****' }));   
$db_c->register(IOC::Service->new('connection' => sub {
              my $c = shift;
              return DBI->connect(
                          $c->get('dsn'), 
                          $c->get('username'), 
                          $c->get('password'));
              }));

$app_c->addSubContainer($db_c);

my $log_c = IOC::Container->new('logging');
$log_c->register(IOC::Service->new('log_file', sub {
  '/var/log/app.log' 
}));
$log_c->register(IOC::Service->new('logger', sub { 
              My::Logger->new((shift)->get('log_file')) 
              }));  

$app_c->addSubContainer($log_c);

my $sec_c = IOC::Container->new('security');
$sec_c->register(IOC::Service->new('authenticator' => sub {
              my $c = shift;
              My::Authenticator->new(
                  $c->find('../database/connection'), 
                  $c->find('../logging/logger')
                  );
              }));

$app_c->addSubContainer($sec_c);

$app_c->register(IOC::Service->new('app' => sub {
              my $c = shift;
              my $app = My::Application->new($c->find('/security/authenticator'));
              $app->setLogger($c->find('/logging/logger'));
              $app->setDatabaseConnection($c->find('/database/connection'));
              return $app;
              }));
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 
Dr. Dobb's TV