Bread:...vanced(3pm)

πŸ“¦
version 0.37 Β· 3 versions
, in debian bookworm, debian trixie, ubuntu noble, ubuntu questing

πŸ’– Bread::Board::Manual::Concepts::Advanced πŸ’–

okay bestie, welcome to the advanced level of the Bread::Board manual β€” this is the Bread:...vanced(3pm) page, straight out of the User Contributed Perl Documentation, and it's giving "level 2 of the tutorial, bring snacks" energy. πŸ“

✨ VERSION ✨

version 0.37 β€” yes, she's on 0.37, and she's thriving.


🌸 INTRODUCTION 🌸

so in the Bread::Board::Manual::Concepts doc, we did the whole foundations tour β€” we introduced you to the idea of a container and a service, and showed you how they get used together. we built a hierarchical container that organized different sets of services into what is basically subsystems inside one big app. and like, that alone? already iconic. but you're probably sitting there asking: what about re-use? πŸ‘€

Bread::Board already encourages decoupled object design by removing the need to manually wire your application components together. but what about re-using the Bread::Board components themselves? fr fr, that's the question.

this document is gonna walk you through some of the more advanced concepts in Bread::Board, with the specific focus being re-use and extension. periodt. πŸ’…


πŸ‘‘ ADVANCED CONCEPTS πŸ‘‘

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. 😭 (author is being so real for that)

🧚 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. no gatekeeping, we love to see it.

here's 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. easy peasy 🫢

my $c = My::Application::Container->new(
    name          => 'MyLoggingContainer',
    log_file_name => 'other_logfile.log'
);

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

okay important note bestie, don't skip this: when you call the constructor of a subclass of Bread::Board::Container, you must pass the name attribute as a parameter. that's a requirement, not a suggestion. βœ‹

additionally, you could use the +name syntax in the subclass itself, like so:

has '+name' => ( default => 'MyLoggingContainer' );

which will remove that requirement in the constructor β€” unless you choose to override it. your call, main character. πŸ’…

it's also possible to extend/specialize a Bread::Board::Service type to customize it for your needs.

more to come later. (teaser era ✨)

🌈 Parameterized Containers

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're 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. it's giving function, but make it containers. πŸ¦‹

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. lowkey flexible, highkey powerful. πŸ’œ

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. slay. πŸ”₯


πŸ’œ 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. (yes bestie, an actual repro, not just vibes 😭)

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 the whole page, babe β€” this doc came to you from perl v5.36.0, dated 2022-12-12, under the Bread:...vanced(3pm) name. go build something cute with it. πŸ‘‘βœ¨

πŸ“¦ where this page comes from