Bread:...ncepts(3pm)
π Bread::Board::Manual::Concepts π
okay bestie, so this is Bread::Board::Manual::Concepts from the User Contributed Perl Documentation section (it's a 3pm page, iykyk), and it's literally the manual that explains every concept in Bread::Board. grab a snack, we're doing a whole tour π«Ά
β¨ VERSION β¨
version 0.37 β yes we're on 0.37, keep up π
π INTRODUCTION π
So this document is doing the absolute most to explain the central concepts of Bread::Board, and like, how they all work together to manage both object lifecycles and object dependencies. iconic behavior honestly.
And listen β in this doc we're using the raw OO syntax of Bread::Board, on purpose. Why? because we want the concepts to be crystal clear and NOT clouded by syntactic sugar, okay? We only bring in the sugar layer at the very end, and by then you're gonna be like "ohhhh THAT's what's happening under the hood" β¨
π CONCEPTS π
π« What is Inversion of Control? π«
Okay so Inversion of Control (or IoC for the girlies in a hurry) is literally the simplest idea ever: you release control of some part of your application over to some other part of your application. Could be your own code, could be an outside framework. That's it. That's the concept π
IoC is a super common paradigm in GUI frameworks β you give up control of your application flow to the framework, and you install your code at callback hooks inside the framework. Let's compare, bestie:
- Command line interface: the application asks a question, the user responds, the application processes the answer, asks another question, and so on and so on until it's done. Very linear, very demure.
- GUI approach for the same app: the application displays a screen and goes into an event loop, and user actions get processed with event handlers and callback functions. The GUI framework has inverted control of the application flow and relieved your code from having to deal with it at all. main character framework energy, honestly.
Now β IoC is also sometimes called 'Dependency Injection' or the 'Dependency Injection Principle', and so many people confuse the two. But no, they are NOT the same thing. The concepts behind dependency injection are actually just an example of IoC principles in action, specifically about your application's dependency relationships. Also, IoC is sometimes called the Hollywood Principle because of the whole "don't call us, we'll call you" vibe of callbacks and event handlers. π
Howard Lewis Ship, the creator of the HiveMind IoC Framework, once said dependency injection is basically 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 hand over control of object creation, which also includes satisfying your dependency relationships
The following sections explain the basic concepts around Bread::Board and how it relates to IoC. let's gooo πΈ
π§Ί Containers π§Ί
The central part of just about any IoC framework is the container. A container's responsibilities are roughly:
- dispense services π
- handle the resolution of said service's dependency relationships π¦
First, we start with a simple container for our services to live in. We give the container a name so we can address it later β think of it like a package namespace.
my $c = Bread::Board::Container->new( name => 'Application' );
Next we need to add a service to that container (we'll explain services a lil later, promise).
$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 then we 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 looks up the service you asked for and returns the instance, which is basically equivalent to those chained fetch and get calls above. same thing, fewer steps, slay β¨
π Dependency Management π
Dependency management is also quite simple, and it's easiest to show with an example. But first, let's 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 let's 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 that we can access the resolved dependencies and use them in our Authenticator object's constructor. chef's kiss π
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 do you need to worry whether your database is connected or your logger initialized, or in what order you need to do that initialization β Bread::Board handles ALL of that for you, including circular dependencies. This might 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. trust. π
β»οΈ 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 get a new instance back. There's also another option for lifecycle management that we call 'Singleton'. Here's how we'd use the 'Singleton' lifecycle to make sure you always get back the same logger instance:
$c->add_service(
Bread::Board::BlockInjection->new(
lifecycle => 'Singleton',
name => 'logger',
block => sub { Logger->new() }
)
);
Now every time we request a new logger component from our container, we get the exact same instance. Being able to switch between different lifecycles by just changing one service parameter comes in very handy as your application grows. And extending that idea β you can totally see how you could create your own custom service objects to manage your specific lifecycle needs, like a pool of database connections. obsessed π
π Services π
Up until now we've shown the default way of creating a service, using Bread::Board::BlockInjection and an anonymous subroutine. But that is NOT the only way to go, bestie.
If you've ever encountered IoC in the Java world, you might know that there are 3 'types' of IoC/Dependency Injection:
- Constructor Injection
- Setter Injection
- Interface Injection
In Bread::Board we support both Constructor and Setter injection. It's the author's opinion though that Interface injection was not only too complex, but highly Java specific, and the concept just didn't adapt itself well to perl. so we said no thanks π
πΈ Block Injection πΈ
While not one of 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 than 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 β when supplied, it performs a basic type check on the result of the subroutine block. cute AND responsible π
πΌ 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's called a "Good Citizen" object β an object who is completely initialized upon construction. very well-behaved, very demure π
$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's our earlier authenticator service rewritten to use constructor injection β this is by far the simplest injection type, since it requires little more than a class name and a HASH of dependencies. β¨
β¨ Setter Injection β¨
Okay so Bread::Board also does Setter Injection, bestie π The whole vibe here is: for every component dependency, there has to be a matching setter method hanging out. This style got super popular because of the Spring java framework, iykyk. Ngl though β the author literally says he doesn't find this type of injection as useful as block or constructor injection π but it can come in clutch if your object is the type that wants you to call setters to initialize it. Here's a pretty 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
)
}
)
);
So what setter injection actually does is it creates the object without passing any arguments to the constructor, then loops through the keys in the dependency HASH, treats each key as a method name, and each value as that method's argument. So in this case, the above is literally equivalent to doing:
my $json = JSON->new;
$json->utf8(1);
$json->pretty(1);
Now, 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 instead. Bestie, 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. Periodt π
ποΈ Hierarchical Containers ποΈ
Up until now, we've only seen basic containers which only have a single level of components. But as your application grows larger (and she will, main character energy π), 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's 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 you can see in the example above, hierarchical containers can be used as a form of namespacing to organize your Bread::Board configuration better π As it's shown with the 'authenticator' service, it's 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 that they contain a 'db_conn' and 'logger' service respectively. And as is shown in the 'app' service, it's also possible to address services using an absolute path notation. Iconic honestly π
π¬ Sugar Layer π¬
So, up until now we've been creating all our Bread::Board objects by hand π and as you can tell, this is both verbose and tedious. To make your life easier, Bread::Board</p> provides a simple sugar layer over these objects π Here's 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 β¨ obsessed, honestly π
π SEE ALSO π
This article is based on an article the author wrote for The Perl Journal about his 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. No cap, that part matters π
Β©οΈ 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 πΈ
And that's a wrap bestie π this whole page is perl v5.36.0, dated 2022-12-12 β go forth and inject those dependencies, you absolute icon ππ β¨
π¦ where this page comes from
- debian bookworm: libbread-board-perl 0.37-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- debian trixie: libbread-board-perl 0.37-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: libbread-board-perl 0.37-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu questing: libbread-board-perl 0.37-2
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms