# Resource::Silo A lazy declarative resource container for Perl. # DESCRIPTION Declare resources such as configuration files, database connections, external service endpoints, and so on, using a simple syntax, and manage their lifecycle. ## Syntax: * `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. ## Inside the application: * 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. ## In test files: * overriding actual resources with mocks/fixtures; * locking the container so that no unmocked resources can be acquired. ## In support scripts and tools: * 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. ## Overall: * built with simplicity and performance in mind; * robust and resilient to misuse; * terse, concise, and laconic. # USAGE Declaring a resource: ```perl 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: ```perl 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: ```perl 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: ```perl 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: ```perl 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: ```perl 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. ``` # INSTALLATION To install this module, run the following commands: perl Makefile.PL make make test make install # ACKNOWLEDGEMENTS The library was named after a building in the game *Heroes of Might and Magic III: The Restoration of Erathia*. # LICENSE AND COPYRIGHT This software is free software. Copyright (c) 2023 Konstantin Uvarin (khedin@gmail.com).