/ domm

I hack Perl for fun and
profit.

Follow me on twitter!
Atom Icom ... on Atom!
<<<<<<<<<<
12.04.2013: Is Bread::Board the right tool for this job?

This post is a question / cry for help!

We're currently working on a new app that's going to need several components (web frontend, web admin interface, lots of small to big workers connected via ZeroMQ, cron jobs, ...). These components need to access various other services (DB, logging, templates, caches, ..).
So instead of defining these services in lazy_build attributes that live in roles and are applied to the components, we though we'd give Bread::Board a try. We also consider to use OX instead of Catalyst, and OX uses (no, is) Bread::Board.

Our idea is to define a set of services (DB, templates, ..) and their dependencies. Each component would also be a service (or a container?) and depend on the services it needs.

In a script that runs a worker, we could then do:

use OurApp::BreadBoard;
my $bb = OurApp::BreadBoard->new( name=>'SomeWorker' );
my $app = $bb->resolve( service => 'Apps/some_worker' );
$app->run;

This seems to work.

But...

We have different environments: production, development, testing, ...

In production, the workers are distributed over several different machines. They use some fancy 0mq service discovery to figure out where to connect to.

In development we want all the workers to run on the same machine. So we need a trimmed-down version of the service discovery. We also want to use different databases. Log somewhere else. etc

So how can we tell Bread::Board to use different services (or to create the same services differently)?

Parameters

You can pass parameters to a service when you resolve it:

https://metacpan.org/source/STEVAN/Bread-Board-0.25/t/041_parameter_cache_handling.t
line 41:   $c->resolve( service => 'foo', parameters => { bar => 10 } );

But it does not seem to be possible to pass a parameter to ALL services, at least if I understand Bug #77229 correctly.

Stevans suggestion to just also resolve the dependency might work, but is sure very ugly (especially if you need to do it on several services/deps). I could just as well generate the objects myself and pass them around...

Bread::Board::Container::Parameterized

I cannot even tell is this might be a solution because it lacks docs, and I cannot figure out what it should do from the tests.

Moose attribute

Bread::Board is based on Moose. So we can add a regular attribute to our Bread::Board class and use this to discern the environments:

package Our::BreadBoard;
use Moose;
use Bread::Board::Declare;
has '+name' => ( is => 'ro', default => "DefaultName" );

has 'environment' => (
    is      => 'ro',
    isa     => 'Str',
    default => 'production',
    service => 0,
);

service => 0 seems to be crucial here, because this tells Bread::Board::Declare that this attribute is just a plain Moose attrib and no Bread::Board service.

Now I can access this attribute in the blocks building my objects (but accessing it isn't that easy, either..):

package OurApp::BreadBoard;
use Moose;
use Bread::Board::Declare;

has 'zmq_service_discovery' => (
    is    => 'ro',
    isa   => 'OurApp::ServiceDiscovery',
    block => sub {
        my ($service, $container) = @_;
        my $root = $container->fetch('/');

        if ($root->environment eq 'production') {
            return OurApp::ServiceDiscovery::Distributed;
            # or:
            # return OurApp::ServiceDiscover->new(
            #     mode => 'distributed'
            # );
        }
        else {
            return OurApp::ServiceDiscovery::Local;
        }
    },
);

has 'some_worker' => (
    is  => 'ro',
    isa => 'OurApp::Some::Worker',
    dependencies => {
        service_discoverer => 'zmq_service_discovery'
    }
);

# regular script launching the worker
use OurApp::BreadBoard;
my $bb = OurApp::BreadBoard->new( name=>'SomeWorker' );
my $app = $bb->resolve( service => 'Apps/some_worker' );
$app->run;

# a "meta" script that starts all components, each running in a fork
# this should also start the web front/backend (via plackup)
use OurApp::BreadBoard;
my $bb = OurApp::BreadBoard->new(
    name=>'MetaRunner',
    environment=>'devel'
);
foreach my $app (qw[some_worker]) {
    if (my $pid = fork()) {
        # parent, manage childs etc
    }
    else {
        my $app = $bb->resolve( service => 'Apps/'.$app );
        $app->run;
    }
}

The Questions

Is this approach sane? Are there better / native ways to pass options deep into a Bread::Board stack? Is Bread::Board the completely wrong tool? We'd really appreciate any feedback / comments!

Comments (via disqus)

30.03.2013: Acme::ReturnValue 1.001
28.03.2013: Blio featured on Perl maven
18.03.2013: Things I learned at German Perl Workshop 2013
05.03.2013: Vienna.pm Techmeet
25.02.2013: Module::ExtractUse 0.29
24.01.2013: So many Perl workshops
20.01.2013: Blio updates
07.12.2012: Getting started with ZeroMQ and AnyEvent
19.11.2012: Austrian Perl Workshop 2012
>>>>>>>>>>