NAME
Context::Singleton::Tutorial - How to work with Context::Singleton
TASK
Let's have an application handling web requests and emails, eg ticket system.
Application has config file providing database credentials.
IMPLEMENTATION
Root context resources
Root context is default context provided by Context::Singleton. Resources defined in this context are real singletons.
Rule config_file
# 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.
Resource db_dsn
Resource db_user
Resource db_password
DSN is provided by config file via method db_dsn
. Similar for db_user
and 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',
);
Resource db
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).
User authentication
Let's assume our application identifies user by email address or expects authenticated HTTP request.
Resource user
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.
EXTENDING - DB PER USER
Let's upgrage our application so we will have one database per user but still using central database for user authentication.
- Override resources after using them
-
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 ...; }; }
- Change user db rule and link it with db
-
# 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} }, );