2012 Feed

More Bread::Board

OX - 2012-12-14

Typemaps and Dependency Inference

Most of the classes in your application will probably only need to be instantiated in one way. You probably won't have multiple instances of a model class, for instance. This means that when you mention the model class in one of your controller classes, it's unambiguous what you're actually asking for - there's no reason that you should need to explicitly list it as a dependency, since it's already listed as the type constraint in the controller. Bread::Board understands this, and can be used to automatically introspect the dependency list for your classes via the attributes that they declare.

Given an application like this:


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: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 

 

package MyApp::Model;
use Moose;

has dsn => (
    is => 'ro',
    isa => 'Str',
    required => 1,
);

package MyApp::Controller;
use Moose;

has model => (
    is => 'ro',
    isa => 'MyApp::Model',
    required => 1,
);

# ...

package MyApp;
use OX;

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

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

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

router as {
    # ...
};

 

we can instead tell Bread::Board to infer the dependencies for the controller service:


1: 
2: 
3: 
4: 
5: 

 

has controller => (
    is => 'ro',
    isa => 'MyApp::Controller',
    infer => 1,
);

 

This looks through all of the required attributes in MyApp::Controller, finds the ones with type constraints that correspond to classes, and looks for services in the OX application whose type constraints correspond to those classes. Any services that it finds are automatically added to the list of dependencies that the service explicitly specifies (if any). This can't entirely replace manually specifying dependencies, but it can greatly simplify them.

Lifecycles

By default, any time a service is resolved, a new instance is created. This is generally what you want (to avoid inadvertently leaking data between requests), but sometimes persisting data is necessary. For instance, you probably don't want to reconnect to the database on every single request, if performance is important for your application. In this case, you can specify a different lifecycle for a particular service:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 

 

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

has dbh => (
    is => 'ro',
    dependencies => ['dsn'],
    lifecycle => 'Singleton',
    block => sub {
        my $s = shift;
        return DBI->connect($s->param('dsn'));
    },
);

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

 

Here, the dbh service will be instantiated the first time that it is requested, but then every subsequent time, it will return the same instance. In this example, every time the model service is resolved, a new MyApp::Model instance will be created, but each of those instances will use the same dbh.

In addition to the Singleton lifecycle, you can also specify a Request lifecycle. This will act like Singleton, except that the cached value will be cleared between requests.

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