NOTE: This is just a quick sketch of these docs, more to come in the next few releases, for now I need to
get this one out the door.
Subclassing
Bread::Board was built from the very start to be an open system and to allow for the subclassing of all
its internal components.
Here is a simple example of extending Bread::Board::Container to build a container specific to your
application.
package My::Application::Container;
use Moose;
use Bread::Board;
extends 'Bread::Board::Container';
has 'log_file_name' => (
is => 'ro',
isa => 'Str',
default => 'logfile.log',
);
sub BUILD {
my $self = shift;
container $self => as {
service 'log_file' => $self->log_file_name;
service 'logger' => (
class => 'My::FileLogger',
lifecycle => 'Singleton',
dependencies => {
log_file => depends_on('log_file'),
}
);
service 'application' => (
class => 'My::Application',
dependencies => {
logger => depends_on('logger'),
}
);
};
}
Then you can simply create an instance of the container and instantiate an instance of the application.
my $c = My::Application::Container->new(
name => 'MyLoggingContainer',
log_file_name => 'other_logfile.log'
);
my $app = $c->resolve( service => 'application');
It should be noted that when calling the constructor of a subclass of Bread::Board::Container, you must
pass the "name" attribute as a parameter. Additionally you could use the "+name" syntax in the subclass
itself like so:
has '+name' => ( default => 'MyLoggingContainer' );
which will remove the requirement in the constructor unless you choose to override it.
It is also possible to extend/specialize a Bread::Board::Service type to customize it for your needs.
More to come later.
ParameterizedContainers
Extending containers is just one form of re-use, just like extending a class in plain old OOP. But
Bread::Board also provides another means of re-use, and that is parameterized containers.
If you are familiar with functors in Standard ML or O'Caml then this might look familiar to you. A
parameterized container is basically a container which expects another container (or containers) as an
argument and produces a third container as the result.
Lets take a simple example here of a Logger object which logs to a database.
my $db_logger = container 'DatabaseLogger' => [ 'DBConnInfo' ] => as {
service 'handle' => (
class => 'My::Database::Logger',
dependencies => {
dsn => depends_on('DBConnInfo/dsn'),
username => depends_on('DBConnInfo/username'),
password => depends_on('DBConnInfo/password'),
}
);
};
It is parameterized with a "DBConnInfo" container which has three services, a "dsn", a "username" and a
"password". Now let's create a simple container which fulfills these requirements.
my $db_conn_info = container 'DatabaseConnection' => as {
service 'dsn' => 'dbi:mysql:foo';
service 'username' => 'bar';
service 'password' => '***';
};
The above container fulfills the bare minimum, but this could have just as easily have been a much more
complex container which also had a service for a DBIx::Class schema, or a KiokuDB directory object. As
long as the container provided the three required services, that was all that the "DatabaseLogger"
parameterized container required.
Now, a parameterized container is not a usable container, you must create an instance of it. That is as
simple as calling the "create" method, like so.
my $my_db_logger = $db_logger->create(
DBConnInfo => $db_conn_info
);
After which you can use it just like any other Bread::Board container would be used.
my $log_handle = $my_db_logger->resolve(
service => 'handle'
);
Parameterized containers can also be nested, here is an example of an Application container that expects
a Logger.
my $app = container 'Application' => [ 'Logger' ] => as {
service 'app' => (
class => 'My::Application',
dependencies => {
log_handle => depends_on('Logger/handle')
}
);
};
And here we instantiate an instance of our Application container using the DatabaseLogger.
my $db_app = $app->create(
Logger => $db_logger->create(
DBConnInfo => $db_conn_info
)
);
And of course, since the Logger is a parameter we could just as easily pass in a simpler screen logger
for a test environment or something. Here is what that would look like.
my $simple_logger = container 'SimpleLogger' => as {
service 'handle' => (
class => 'My::Simple::Logger'
);
};
my $simple_app = $app->create(
Logger => $simple_logger
);
Parameterized containers provide a useful and powerful means of re-use and abstraction, making it easy to
create flexible containers to model your applications subsystems.