2012 Feed

Bread::Board

OX - 2012-12-04

Dependency Injection

One thing that you may have noticed in the article about actions is that actions only receive the request object. There is no global application context, or stash, or anything like that, like you may be used to in other frameworks. This is by design - global state in general is bad for maintainability.

This does present a problem, however - how do you access things like your model objects, if action methods aren't passed anything which can access them? The answer is pretty straightforward: if your controller is going to use a model object, that object should be an attribute. This limits the scope of your helper objects to only the places you expect them to actually be used (which helps in debugging, and reliability in general), without making them any more difficult to access.

The mechanism which powers this is called dependency injection. Dependency injection is a pattern in which you specify the relationships between your objects (which objects are necessary for each objects' construction), and a framework determines how to create the objects on demand such that all of the dependencies are met. The framework that OX uses is called Bread::Board, via the Bread::Board::Declare sugar layer.

Defining services

Describing your application is done by creating services for each type of object your application will need. In OX (because OX uses Bread::Board::Declare), this is as simple as creating attributes on your application class, just like you would in any other project. The difference is that in the attribute definition, you must define how the value for that attribute should be created, and which other attributes the creation process needs. Here is an example of an OX class with a few attribute defintions:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 

 

package MyApp;
use OX;

has dsn => (
    is => 'ro',
    isa => 'Str',
    value => 'dbi:mysql:myapp',
);

has model => (
    is => 'ro',
    isa => 'MyApp::Model',
    dependencies => ['dsn'],
    block => sub {
        my $s = shift;
        return MyApp::Model->connect($s->param('dsn'));
    },
);

has controller => (
    is => 'ro',
    isa => 'MyApp::Controller',
    dependencies => ['model'],
);

router as {
    # ...
};

 

This is very similar to the way you would write the class normally, except that you use the value or block options instead of default or builder, and if your attribute holds an object with a constructor that takes key/value pairs (like Moose objects do by default), you don't have to specify anything at all (Bread::Board will figure out how to create the object on its own).


1: 
2: 
3: 
4: 
5: 

 

has dsn => (
    is => 'ro',
    isa => 'Str',
    value => 'dbi:mysql:myapp',
);

 

If the attribute specifies value, then requesting the service will return that given value directly. Here, requesting the dsn service will return the string "dbi:mysql:myapp" unless another value has been provided for this attribute, such as from a constructor parameter.


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 

 

has model => (
    is => 'ro',
    isa => 'MyApp::Model',
    dependencies => ['dsn'],
    block => sub {
        my $s = shift;
        return MyApp::Model->connect($s->param('dsn'));
    },
);

 

If you specify block, Bread::Board will first initialize all of the dependencies. Then, it will call the subroutine provided in block. That subroutine receives one argument, an object which contains the value of each dependency. The dependency values can be accessed via the param method on that object. Here, requesting the model service will first get the value from the dsn service, and then call the given sub. Calling $s->param('dsn') inside that sub will return the value for the dsn service.


1: 
2: 
3: 
4: 
5: 

 

has controller => (
    is => 'ro',
    isa => 'MyApp::Controller',
    dependencies => ['model'],
);

 

If you specify neither of those, but the type constraint corresponds to a class, Bread::Board will get the value associated with each dependency listed and pass them as parameters to the constructor. For instance, here the value for the service will be created by calling MyApp::Controller->new(model => $model) (where $model is the value retrieved from the model service).

Dependencies

Dependencies are how services know which other services are required in order to generate their value. The simplest way to specify dependencies is with an arrayref of service names (as above). If you need to use a different name for a service, however, you'll need to specify the dependencies via a hashref:


1: 
2: 
3: 
4: 
5: 
6: 
7: 

 

has view => (
    is => 'ro',
    isa => 'Template',
    dependencies => {
        INCLUDE_PATH => 'template_root',
    },
);

 

This will create a Template object by calling Template->new(INCLUDE_PATH => $template_root), where $template_root is created from the template_root service.

Sometimes you don't necessarily want to create trivial services for constant values. If you want to create a service with an inline, hardcoded value for one of its constructor parameters, you can use the literal helper keyword:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 

 

has view => (
    is => 'ro',
    isa => 'Template',
    dependencies => {
        INCLUDE_PATH => 'template_root',
        TAG_STYLE => literal('php'),
    },
);

 

This will do the same thing as above, except that it will call Template->new(INCLUDE_PATH => $template_root, TAG_STYLE => 'php').

Resolving services

Requesting the value from a service is called resolving the service. Recall from the article on route builders that there are many ways to specify which code executes when a route matches. For instance, you can specify an action like "profile.edit". To dispatch this action, OX first resolves the profile service, which by convention we also call the profile controller since it is a kind of dispatch target. OX then calls the edit method (called the edit action) on the profile controller. Thanks to Bread::Board, your profile controller will be fully initialized with all of its specified dependencies, so that OX can satisfy the user's web request.

If you want to get the values out of your application class separately, you can just call the accessor for the attribute. If the attribute doesn't have a value stored in it, this will create a new instance of the value held by the service and return it (otherwise, it will work like a normal attribute). Other ways to interact with services will be described in a future article.

Further reading

This article has been just a very high level overview of how to create OX applications that use additional classes. We will be going deeper into detail about how to use Bread::Board features more effectively with OX in future articles, but if you're interested in learning more about it, the documentation for Bread::Board (especially the Bread::Board::Manual) and Bread::Board::Declare are useful resources, as is this talk from YAPC::NA 2012.

Gravatar Image This article contributed by: Jesse Luehrs <jesse.luehrs@iinteractive.com>