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 )