Context-Singleton
view release on metacpan or search on metacpan
lib/Context/Singleton/Tutorial.pod view on Meta::CPAN
=encoding utf-8
=head1 NAME
Context::Singleton::Tutorial - How to work with Context::Singleton
=head1 TASK
Let's have an application handling web requests and emails, eg ticket system.
Application has config file providing database credentials.
=head1 IMPLEMENTATION
=head2 Root context resources
Root context is default context provided by Context::Singleton.
Resources defined in this context are real singletons.
=head3 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 I<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 C<behaviour immutability> of resources and provides way how
to inject new behaviour and simplifies reusability.
=head3 Resource db_dsn
lib/Context/Singleton/Tutorial.pod view on Meta::CPAN
class => 'DBI',
builder => 'connect',
dep => [ 'db_dsn', 'db_user', 'db_password' ],
);
Since now every code can fetch I<db> value (valid in its context).
=head2 User authentication
Let's assume our application identifies user by email address or expects
authenticated HTTP request.
=head3 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 I<email_address> whereas HTTP handler provides I<http_request>.
In both cases rule I<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 I<user> available in any descending level
cached it in frame where I<email_address>/I<http_request> is defined.
=head1 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.
=over
=item 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
...;
};
}
=item 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} },
);
=back
( run in 0.847 second using v1.01-cache-2.11-cpan-39bf76dae61 )