A lazy declarative resource container for Perl.
Declare resources such as configuration files, database connections, external service endpoints, and so on, using a simple syntax, and manage their lifecycle.
resource
- a Moose-like prototyped function to declare resources inside a sealed container class;silo
- a re-exportable function to access the one and true container instance;- no limitations on creating more than one container objects or even classes.
- acquiring resources on demand as simple as
silo->my_foo
; - caching them;
- releasing them in due order when program ends;
- preloading all resources at startup to fail early;
- detecting forks and reinitializing to avoid clashes.
- overriding actual resources with mocks/fixtures;
- locking the container so that no unmocked resources can be acquired.
- loading only the needed resources for fast startup time;
- creating isolated one-off resource instances to perform invasive operations such as a big DB update within a transaction.
- built with simplicity and performance in mind;
- robust and resilient to misuse;
- terse, concise, and laconic.
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 =>
derived => 1, # merely a frontend to DBI
require => 'My::App::Schema', # load module before init
init => sub {
my $self = shift;
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_conn => sub {
my $self = shift;
Redis->new( server => $self->config->{redis} );
};
resource redis =>
argument => sub { $known_namespaces{ $_ } },
init => sub {
my ($self, $name, $ns) = @_;
Redis::Namespace->new(
redis => $self->redis,
namespace => $ns,
);
};
# later in the code
silo->redis; # nope!
silo->redis('session'); # get a prefixed namespace
Using it elsewhere:
use My::App qw(silo);
sub load_foo {
my $id = shift;
my $sql = q{SELECT * FROM foo WHERE foo_id = ?};
silo->dbh->fetchrow_hashref( $sql, $id );
};
Using it 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.
To install this module, run the following commands:
perl Makefile.PL
make
make test
make install
The library was named after a building in the game Heroes of Might and Magic III: The Restoration of Erathia.
This software is free software.
Copyright (c) 2023 Konstantin Uvarin (khedin@gmail.com).