Resource::Silo - declarative lazy resource container library
Resource::Silo is a declarative lazy resource container library for Perl. It allows to declare resources such as configuration files, database connections, external service endpoints, and so on, in one place; acquire them on demand; cache them; and release in due order.
It may also be described as the single source of truth for the application's side effects.
For those unfamiliar with Bread::Board:
Hey, hey, hey, hey! A shiny new solution just arrived!
- declare all of your application's resources / dependencies in one place using a simple DSL;
- instantiate them on demand and only once, with little overhead;
- override them with mocks in your tests & guard against unexpected side effects;
- gain more fine-grained control via the
ctl
frontend.
For Bread::Board users:
- the container object is blessed into a one-off container class;
- service names become methods in said class, specifically lazy getters;
- syntax for service declaration is more Moose-like;
- the only service initializer available is
block
; - DI/IoC is not imposed onto the user but merely suggested;
- service cleanup order may be specified;
- services are reinitialized if a fork is detected;
- services can be replaced with mocks for testing.
The usage/syntax examples:
Declaring a resource:
package My::App;
use Resource::Silo;
resource config => sub {
require YAML::XS;
YAML::XS::LoadFile( "/etc/myapp.yaml" );
};
resource dbh => sub {
require DBI;
my $self = shift;
my $conf = $self->config->{database};
DBI->connect(
$conf->{dbi}, $conf->{username}, $conf->{password}, { RaiseError => 1 }
);
};
resource user_agent => sub {
require LWP::UserAgent;
LWP::UserAgent->new();
# set your custon UserAgent header or SSL certificate(s) here
};
Resources with more options:
resource logger =>
cleanup_order => 9e9, # destroy as late as possible
init => sub { ... };
resource schema =>
derivative => 1, # merely a frontend to its dependencies
init => sub {
my $self = shift;
require My::App::Schema;
return My::App::Schema->connect( sub { $self->dbh } );
};
Declaring a parametric resource:
package My::App;
use Resource::Silo;
use Redis;
use Redis::Namespace;
my %known_namespaces = (
lock => 1,
session => 1,
user => 1,
);
resource redis =>
argument => sub { $known_namespaces{ $_ } },
init => sub {
my ($self, $name, $ns) = @_;
Redis::Namespace->new(
redis => $self->redis,
namespace => $ns,
);
};
resource redis_conn => sub {
my $self = shift;
Redis->new( server => $self->config->{redis} );
};
# later in the code
silo->redis; # nope!
silo->redis('session'); # get a prefixed namespace
Using resources throughout the project:
use My::App qw(silo);
sub load_foo {
my $id = shift;
my $sql = q{SELECT * FROM foo WHERE foo_id = ?};
silo->dbh->fetchrow_hashred( $sql, $id );
};
Using as a dependency injection:
package My::App::Stuff;
use Moo;
use My::App qw(silo);
has dbh => is => 'lazy', builder => sub { silo->dbh };
Overriding resources in test files:
use Test::More;
use My::App qw(silo);
silo->ctl->override( dbh => $temp_sqlite_connection );
silo->ctl->lock;
my $stuff = My::App::Stuff->new();
$stuff->frobnicate( ... ); # will only affect the sqlite instance
$stuff->ping_partner_api(); # oops! the user_agent resource wasn't
# overridden, so there'll be an exception
Performing a Big Scary Update:
use My::App qw(silo);
my $dbh = silo->ctl->fresh('dbh');
$dbh->begin_work;
# any operations on $dbh won't interfere with normal usage
# of silo->dbh by other application classes.
Conclusion:
This module is still early in development. The API is not yet set in stone. Feel free to try it out and report the missing features that would make it more applicable to new projects as well as old, entrenched code bases.
Leave a comment