Skip site navigation (1)Skip section navigation (2)

FreeBSD Manual Pages

  
 
  

home | help
Bread::Board::Manual::UsereContributed Perl DBread::Board::Manual::Concepts(3)

NAME
       Bread::Board::Manual::Concepts -	An overview of the concepts in
       Bread::Board

VERSION
       version 0.37

INTRODUCTION
       This document attempts to convey	the central concepts of	Bread::Board
       and show	how they work together to manage both object lifecycles	and
       object dependencies.

       In this document	we use the raw OO syntax of Bread::Board, this is so
       that the	concepts being illustrated are not clouded by syntactic	sugar.
       We only introduce the sugar layer at the	end, at	which point we hope
       that it will become clear what is going on "under the hood" when	you
       use it.

CONCEPTS
   What	is Inversion of	Control?
       Inversion of Control (or	IoC) is	the very simple	idea of	releasing
       control of some part of your application	over to	some other part	of
       your application, be it your code or an outside framework.

       IoC is a	common paradigm	in GUI frameworks, whereby you give up control
       of your application flow	to the framework and install your code at
       callbacks hooks within the framework. For example, take a very simple
       command line interface; the application asks a question,	the user
       responds, the application processes the answer and asks another
       question, and so	on until it is done. Now consider the GUI approach for
       the same	application; the application displays a	screen and goes	into
       an event	loop, users actions are	processed with event handlers and
       callback	functions. The GUI framework has inverted the control of the
       application flow	and relieved your code from having to deal with	it.

       IoC is also sometimes referred to as 'Dependency	Injection' or the
       'Dependency Injection Principle', and many people confused the two.
       However IoC and dependency injection are	not the	same, in fact the
       concepts	behind dependency injection are	actually just an example of
       IoC principles in action, in particular about your applications
       dependency relationships. IoC is	also sometimes referred	to as the
       Hollywood Principle because of the don't	call us	we'll call you
       approach	of things like callback	functions and event handlers.

       Howard Lewis Ship, the creator of the HiveMind IoC Framework, once
       referred	to dependency injection	as being the inverse of	garbage
       collection. With	garbage	collection you hand over the details of	the
       destruction of your objects to the garbage collector. With dependency
       injection you are handing over control of object	creation, which	also
       includes	the satisfaction of your dependency relationships.

       The following sections will explain the basis concepts around the
       Bread::Board and	how it relates to the concept of IoC.

   Containers
       The central part	of just	about any IoC framework	is the container.  A
       container's responsibilities are	roughly	to dispense services and to
       handle the resolution of	said service's dependency relationships.

       First we	can start with a simple	container for our services to live in.
       We give the container a name so that we can address it later on,	think
       of this like a package namespace.

	 my $c = Bread::Board::Container->new( name => 'Application' );

       Next we need to add a service to	that container (we will	explain
       services	a little later on).

	 $c->add_service(
	     Bread::Board::BlockInjection->new(
		 name  => 'logger',
		 block => sub {	Logger->new() }
	     )
	 );

       Now if we want an instance of our 'logger' service, we simply ask the
       container for it.

	 my $logger_service = $c->fetch('logger');

       And we then can ask the service to give us an instance of our Logger
       object.

	 my $logger = $logger_service->get;

       Or if we	want to	make this even simpler we can use the "resolve"	method
       of the container	object.

	 my $logger = $c->resolve( service => 'logger' );

       The "resolve" method will look up the service asked for and return the
       instance, which is basically equivalent to the chained "fetch" and
       "get" calls above.

   Dependency Management
       Dependency management is	also quite simple, and is easily shown with an
       example.	But first lets create another component	for our	container, a
       database	connection.

	 $c->add_service(
	     Bread::Board::BlockInjection->new(
		 name  => 'db_conn',
		 block => sub {	DBI->connect('dbi:mysql:test', '', '') }
	     )
	 );

       Now lets	add an authenticator to	our container. The authenticator
       requires	both a database	connection and a logger	instance in its
       constructor. We specify dependency relationships	between	services by
       providing a HASH	of Bread::Board::Dependency objects which themselves
       have a path to the services they	depend upon. In	this case since	all
       these services are in the same container, the service path is simply
       the name.

	 $c->add_service(
	     Bread::Board::BlockInjection->new(
		 name  => 'authenticator',
		 block => sub {
		       my $service = shift;
		       Authenticator->new(
			   db_conn => $service->param('db_conn'),
			   logger  => $service->param('logger')
		       );
		 },
		 dependencies => {
		     db_conn =>	Bread::Board::Dependency->new(
			 service_path => 'db_conn'
		     ),
		     logger  =>	Bread::Board::Dependency->new(
			 service_path => 'logger'
		     ),
		 }
	     )
	 );

       As you can see, the first argument to our service subroutine is
       actually	our service instance. Through this we can access the resolved
       dependencies and	use them in our	Authenticator object's constructor.

       The above example is deceptively	simple,	but really powerful.  What you
       don't see on the	surface	is that	Bread::Board is	completely managing
       initialization order for	you. No	longer to do you need to worry if your
       database	is connected or	your logger initialized	and in what order you
       need to do that initialization, Bread::Board handles that all for you,
       including circular dependencies.	This may not seem terribly interesting
       in such a small example,	but the	larger an application grows, the more
       sensitive it becomes to these kinds of initialization order issues.

   Lifecycle Management
       The default lifecycle for Bread::Board::Service components is a
       'prototype' lifecycle, which means each time we ask for say, the
       logger, we will get a new instance back.	There is also another option
       for lifecycle management	that we	call 'Singleton'. Here is an example
       of how we would use the 'Singleton' lifecycle to	ensure that you	always
       get back	the same logger	instance.

	 $c->add_service(
	     Bread::Board::BlockInjection->new(
		 lifecycle => 'Singleton',
		 name	   => 'logger',
		 block	   => sub { Logger->new() }
	     )
	 );

       Now each	time we	request	a new logger component from our	container we
       will get	the exact same instance. Being able to change between the
       different lifecycles by simply changing one service parameter can come
       in very handy as	you application	grows. Extending this idea, it is
       possible	to see how you could create your own custom service objects to
       manage your specific lifecycle needs, such as a pool of database
       connections.

   Services
       Up until	now, we	have shown the default way of creating a service by
       using the Bread::Board::BlockInjection and an anonymous subroutine. But
       this is not the only way	to go about this. Those	who have encountered
       IoC in the Java world may be familiar with the idea that	there are 3
       'types' of IoC/Dependency Injection; Constructor	Injection, Setter
       Injection, and Interface	Injection.  In Bread::Board we support both
       Constructor and Setter injection, it is the authors opinion though that
       Interface injection was not only	too complex, but highly	java specific
       and the concept did not adapt itself well to perl.

       Block Injection
	   While not in	the 'official' 3 types (mostly because it's not
	   possible in Java), but found	in a few Ruby IoC frameworks,
	   BlockInjection is by	far the	most versatile type. It	simply
	   requires a subroutine and a name and	you do all the rest of it
	   yourself.

	     $c->add_service(
		 Bread::Board::BlockInjection->new(
		     name  => 'logger',
		     class => 'ComplexLogger',
		     block => sub {
			 my $s = shift;
			 my $l = ComplexLogger->new(
			     file => $s->param('log_file')
			 );
			 $l->init_with_timezone( $s->param('timezone') );
			 $l->log_timestamp;
			 $l;
		     },
		     dependencies => {
			 log_file => Bread::Board::Dependency->new(
			     service_path => 'log_file'
			 ),
			 timezone => Bread::Board::Dependency->new(
			     service_path => 'timezone'
			 ),
		     }
		 )
	     );

	   BlockInjection comes	in really handy	when your object requires more
	   then	just constructor parameters and	needs some more	complex
	   initialization code.	As long	as your	subroutine block returns an
	   object, everything else is fair game. Also note the optional
	   'class' parameter, which when supplied will perform a basic type
	   check on the	result of the subroutine block.

       Constructor Injection
	   Bread::Board	also supports Constructor Injection. With constructor
	   injection, the service calls	the class's constructor	and feeds it
	   the dependencies you	specify. This promotes what is called a	"Good
	   Citizen" object, or an object who is	completely initialized upon
	   construction.

	     $c->add_service(
		 Bread::Board::ConstructorInjection->new(
		     name	  => 'authenticator',
		     class	  => 'Authenticator',
		     dependencies => {
			 db_conn => Bread::Board::Dependency->new(
			     service_path => 'db_conn'
			 ),
			 logger	 => Bread::Board::Dependency->new(
			     service_path => 'logger'
			 ),
		     }
		 )
	     );

	   Since Bread::Board is built both with Moose and for use with	Moose
	   objects, it makes the assumption here that the constructor takes
	   named arguments. Here is our	earlier	authenticator service
	   rewritten to	use constructor	injection. This	is by far the simplest
	   injection type as it	requires little	more then a class name and a
	   HASH	of dependencies.

       Setter Injection
	   Bread::Board	also supports Setter Injection.	The idea behind	setter
	   injection is	that for each component	dependency a corresponding
	   setter method must exist. This style	has been popularized by	the
	   Spring java framework. I will be honest, I don't find this type of
	   injection as	useful as block	or constructor,	but it can come	in
	   handy if your object	prefers	you to call setters to initialize it.
	   Here	is a fairly contrived example using the	JSON module.

	     $c->add_service(
		 Bread::Board::SetterInjection->new(
		     name	  => 'json',
		     class	  => 'JSON',
		     dependencies => {
			 utf8	=> Bread::Board::Literal->new(
			     name  => 'true',
			     value => 1
			 )
			 pretty	=> Bread::Board::Literal->new(
			     name  => 'true',
			     value => 1
			 )
		     }
		 )
	     );

	   Setter injection actually creates the object	without	passing	any
	   arguments to	the constructor, then loops through the	keys in	the
	   dependency HASH and treats each key as a method name, and each
	   value as that method's argument. In this case, the above is the
	   equivalent of doing:

	      my $json = JSON->new;
	      $json->utf8(1);
	      $json->pretty(1);

	   You might have been wondering about the fact	we didn't specify
	   Bread::Board::Dependency objects in our dependency HASH, but
	   instead supplied Bread::Board::Literal instances.
	   Bread::Board::Literal is just another Service type that simply
	   holds a literal value, or a constant. When dependencies are
	   specified like this,	Bread::Board internally	converts them into
	   Bread::Board::Dependency whose service is already resolved to that
	   service.

   Hierarchical	Containers
       Up until	now, we	have seen basic	containers which only have a single
       level of	components. As your application	grows larger it	may become
       useful to have a	more hierarchical approach to your containers.
       Bread::Board::Container supports	this behavior through its many sub-
       container methods. Here is an example of	how we might re-arrange	the
       previous	examples using sub-containers.

	 my $app_c = Bread::Board::Container->new( name	=> 'app' );

	 my $db_c = Bread::Board::Container->new( name => 'database' );
	 $db_c->add_service(
	     Bread::Board::BlockInjection->new(
		 name  => 'db_conn'
		 block => sub {
		     my	$s = shift;
		     return DBI->connect(
			 $s->param('dsn'),
			 $s->param('username'),
			 $s->param('password')
		     );
		 },
		 dependencies => {
		     dsn      => Bread::Board::Literal->new(
			 name  => 'dsn',
			 value => 'dbi:mysql:test'
		     ),
		     username => Bread::Board::Literal->new(
			 name  => 'username',
			 value => 'user'
		     ),
		     password => Bread::Board::Literal->new(
			 name  => 'password',
			 value => '****'
		     ),
		 }
	     )
	 );

	 $app_c->add_sub_container( $db_c );

	 my $log_c = Bread::Board::Container->new( name	=> 'logging' );
	 $log_c->add_service(
	     Bread::Board::Literal->new(
		 name  => 'log_file',
		 value => '/var/log/app.log'
	     )
	 );
	 $log_c->add_service(
	     Bread::Board::ConstructorInjection->new(
		 name  => 'logger',
		 class => 'Logger',
		 dependencies => {
		     log_file => Bread::Board::Dependency->new(
			 service_path => 'log_file'
		     )
		 }
	     )
	 );

	 $app_c->add_sub_container( $log_c );

	 my $sec_c = Bread::Board::Container->new( name	=> 'security' );
	 $sec_c->add_service(
	     Bread::Board::ConstructorInjection->new(
		 name	      => 'authenticator',
		 class	      => 'Authenticator',
		 dependencies => {
		     db_conn =>	Bread::Board::Dependency->new(
			 service_path => '../database/db_conn'
		     ),
		     logger  =>	Bread::Board::Dependency->new(
			 service_path => '../logging/logger'
		     ),
		 }
	     )
	 );

	 $app_c->add_sub_container( $sec_c );

	 $app_c->add_service(
	     Bread::Board::ConstructorInjection->new(
		 name	      => 'app',
		 class	      => 'Application',
		 dependencies => {
		     auth    =>	Bread::Board::Dependency->new(
			 service_path => '/security/authenticator'
		     ),
		     db_conn =>	Bread::Board::Dependency->new(
			 service_path => '/database/db_conn'
		     ),
		     logger  =>	Bread::Board::Dependency->new(
			 service_path => '/logging/logger'
		     ),
		 }
	     )
	 );

       So, as an example that can be seen above, hierarchical containers can
       be used as a form of namespacing	to organize your Bread::Board
       configuration better. As	it is shown with the 'authenticator' service,
       it is possible to address services outside of your container using path
       notation. In this case the 'authenticator' service makes	the assumption
       that its	parent container has both a 'database' and a 'logging' sub-
       container and they contain a 'db_conn' and 'logger' service
       respectively. And as is shown in	the 'app' service, it is also possible
       to address services using an absolute path notation.

   Sugar Layer
       So, up until now	we have	been creating all our Bread::Board objects by
       hand. As	you can	tell, this is both verbose and tedious.	To make	your
       life easier, Bread::Board provides a simple sugar layer over these
       objects.	Here is	the equivalent of the above Bread::Board configuration
       using the sugar layer.

	 my $c = container 'app' => as {

	     container 'database' => as	{
		 service 'db_conn' => (
		     block => sub {
			 my $s = shift;
			 return	DBI->connect(
			     $s->param('dsn'),
			     $s->param('username'),
			     $s->param('password')
			 );
		     },
		     dependencies => {
			 dsn	  => ( service 'dsn'	  => 'dbi:mysql:test' ),
			 username => ( service 'username' => 'user' ),
			 password => ( service 'password' => '****' ),
		     }
		 )
	     };

	     container 'logging' => as {
		 service 'log_file' => '/var/log/app.log';
		 service 'logger' => (
		     class	  => 'Logger',
		     dependencies => {
			 log_file => depends_on('log_file'),
		     }
		 )
	      };

	     container 'security' => as	{
		 service 'authenticator' => (
		     class => 'Authenticator',
		     dependencies => {
			 db_conn => depends_on('../database/db_conn'),
			 logger	 => depends_on('../logging/logger'),
		     }
		 )
	     };

	     service 'app' => (
		 class => 'Application',
		 dependencies => {
		     auth    =>	depends_on('/security/authenticator'),
		     db_conn =>	depends_on('/database/db_conn'),
		     logger  =>	depends_on('/logging/logger'),
		 }
	     )
	 };

       As you can see this not only makes the code shorter, but	more
       declarative and easier to read.

SEE ALSO
       This article is based on	an article I wrote for The Perl	Journal	about
       my earlier IOC module. That article can be found	online at
       <http://www.drdobbs.com/windows/184416179>.

AUTHOR
       Stevan Little <stevan@iinteractive.com>

BUGS
       Please report any bugs or feature requests on the bugtracker website
       https://github.com/stevan/BreadBoard/issues

       When submitting a bug or	request, please	include	a test-file or a patch
       to an existing test-file	that illustrates the bug or desired feature.

COPYRIGHT AND LICENSE
       This software is	copyright (c) 2019, 2017, 2016,	2015, 2014, 2013,
       2011, 2009 by Infinity Interactive.

       This is free software; you can redistribute it and/or modify it under
       the same	terms as the Perl 5 programming	language system	itself.

perl v5.32.0			  2019-06-28 Bread::Board::Manual::Concepts(3)

NAME | VERSION | INTRODUCTION | CONCEPTS | SEE ALSO | AUTHOR | BUGS | COPYRIGHT AND LICENSE

Want to link to this manual page? Use this URL:
<https://www.freebsd.org/cgi/man.cgi?query=Bread::Board::Manual::Concepts&sektion=3&manpath=FreeBSD+12.2-RELEASE+and+Ports>

home | help