Context::Singleton::Tutorial - How to work with Context::Singleton
Let's have an application handling web requests and emails, eg ticket system.
Application has config file providing database credentials.
Root context is default context provided by Context::Singleton. Resources defined in this context are real singletons.
# Instance of config file with methods providing configuration options. contrive config_file => ( class => 'My::App::Config', builder => 'parse_file', dep => [ 'config_file_name' ], ); # behaves like sub contrive_config_file { my ($config_file_name) = @_; eval "use My::App::Config" or die $@; return My::App::Config->parse_file ($config_file_name); }
Usage:
# provide config file name proclaim config_file_name => $ENV{MY_APP_CONFIG_FILE}; # when needed just call my $config_file = deduce 'config_file';
Any code in any depth requesting config_file will receive same instance until you will change its dependencies.
frame { # Use different package proclaim 'My::App::Config' => 'My::Other::App::Config'; deduce 'config_file'; }; frame { proclaim 'config_file' => My::Test::Config->new; deduce 'config_file'; };
Why to bother?
As your application evolves you have no idea where you will need given resource neither how many receipts you will add/remove.
Well, you can pass it into every function or every class in chain. You can build and manage all builders on all classes (eg via Moose::Role) You can create it as real singleton as well.
Context::Singleton treats this problem differently. It expects behaviour immutability of resources and provides way how to inject new behaviour and simplifies reusability.
behaviour immutability
DSN is provided by config file via method db_dsn. Similar for db_user and db_password.
db_dsn
db_user
db_password
contrive db_dsn => ( deduce => 'config_file', builder => 'db_dsn', ); contrive db_user => ( deduce => 'config_file', builder => 'db_user', ); contrive db_password => ( deduce => 'config_file', builder => 'db_password', );
DB connection
contrive db => ( class => 'DBI', builder => 'connect', dep => [ 'db_dsn', 'db_user', 'db_password' ], );
Since now every code can fetch db value (valid in its context).
Let's assume our application identifies user by email address or expects authenticated HTTP request.
contrive user => ( deduce => 'http_request', builder => 'user', ); contrive user => ( dep => [ 'db', 'email_address' ], as => sub { my ($db, $address) = @_; # SELECT user FROM users WHERE email = ?; from $db; with $address } );
Email handler provides email_address whereas HTTP handler provides http_request. In both cases rule user can be properly deduced.
# email-handler sub handle_request { frame { proclaim email_address => 'EMAIL_FROM'; do_something; }; } # http handler sub handle_http_request { frame { proclaim http_request => ...; do_something; }; }
Now you have (lazy) resource user available in any descending level cached it in frame where email_address/http_request is defined.
Let's upgrage our application so we will have one database per user but still using central database for user authentication.
sub do_something { my $db = deduce 'db'; my ($dsn, $user, $password) = $db->fetch_user_db_credentials; frame { proclaim db_dsn => $dsn; proclaim db_user => $user; proclaim db_password => $password; # db will be recaluclated when requested ...; }; }
# Provide rule similar do db contrive user_db => (...); contrive user_db_info => ( deduce => 'user_db', as => sub { select .... }, ); # Change db_dsn, db_user, db_password rules contrive db_user => ( dep => [ 'user_db_info' ], as => sub { $_[0]->{db_user} }, );
To install Context::Singleton, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Context::Singleton
CPAN shell
perl -MCPAN -e shell install Context::Singleton
For more information on module installation, please visit the detailed CPAN module installation guide.