class: inverse center middle # Dependency Injection ## Also Introducing: Beam::Wire Doug Bell Chicago.PM (03/28/2013) --- class: center middle # The Setup --- class: middle .center[ # Two Classes in Search of DBI ] ```perl package Foo; use Moo; has dbh => ( is => 'ro', ); ``` ```perl package Bar; use Moo; has dbh => ( is => 'ro', ); ``` --- class: middle .center[ # To Each Their Own ] ```perl package Foo; use Moo; use DBI; has dbh => ( is => 'ro', default => sub { DBI->connect( 'dbi:SQLite:data.db' ); }, ); ``` ```perl package Bar; use Moo; use DBI; has dbh => ( is => 'ro', default => sub { DBI->connect( 'dbi:SQLite:data.db' ); }, ); ``` --- class: middle .center[ # Role Traversal ] ```perl package Role::DBI; use Moo::Role; has dbh => ( is => 'ro', default => sub { DBI->connect( 'dbi:SQLite:data.db' ); }, ); ``` ```perl package Foo; use Moo; use Role::DBI; ``` ```perl package Bar; use Moo; use Role::DBI; ``` --- class: middle .center[ # Singleton ] ```perl package Role::DBI; use Moo::Role; my $dbh; has dbh => ( is => 'ro', default => sub { $dbh ||= DBI->connect( 'dbi:SQLite:data.db' ); }, ); ``` ```perl package Foo; use Moo; use Role::DBI; ``` ```perl package Bar; use Moo; use Role::DBI; ``` --- class: middle .center[ # Singleton Override ] ```perl package Role::DBI; use Moo::Role; my $dbh; has dbh => ( is => 'ro', default => sub { $dbh ||= DBI->connect( 'dbi:SQLite:data.db' ); }, ); package Foo; use Moo; use Role::DBI; package Bar; use Moo; use Role::DBI; package main; use DBI; my $dbh = DBI->connect( '...' ); my $foo = Foo->new( dbh => $dbh ); my $bar = Bar->new( dbh => $dbh ); ``` --- class: middle .center[ # Configuration File ] ```perl # config.yml dsn: 'dbi:SQLite:data.db' ``` ```perl # script.pl use DBI; my $cfg = LoadFile( 'config.yml' ) my $dsn = $cfg->{dsn}; my $dbh = DBI->connect( $dsn ); my $foo = Foo->new( dbh => $dbh ); my $bar = Bar->new( dbh => $dbh ); ``` --- class: middle .center[ # Singleton Config ] ```perl package Role::DBI; use Moo::Role; use YAML::Any qw( LoadFile ); my $dbh; has dbh => ( is => 'ro', default => sub { return $dbh if $dbh; my $cfg = LoadFile( 'config.yml' ) my $dsn = $cfg->{dsn}; return $dbh = DBI->connect( $dsn ); }, ); ``` --- class: middle .center[ # Config Override ] ```perl package Role::DBI; use Moo::Role; use YAML::Any qw( LoadFile ); my $cfg; has cfg_fn => ( is => 'ro', default => 'config.yml' ); has cfg => ( is => 'ro', default => sub { $cfg ||= LoadFile( $_[0]->cfg_fn ) }, ); my $dbh; has dbh => ( is => 'ro', default => sub { return $dbh if $dbh; my ( $self ) = @_; return $dbh = DBI->connect( $self->cfg->{dsn} ); }, ); ``` --- class: middle .center[ # Repeat Ad Nauseum ] * DBI * Caching * ORM * Messaging --- class: inverse center middle # It Gets Complicated --- class: inverse center middle # Give Up! --- class: inverse center middle # Inversion of Control --- class: center middle # "Give Up Control" Pattern --- class: center middle # Dependency Injection --- class: middle .center[ # Container ] * Java * Spring * Google Guice * Perl * IoC * Bread::Board * Beam::Wire --- class: center middle # Container Builds Objects (Services) --- class: center middle # Container Builds Objects' Dependencies --- class: center middle # You Get The Result --- class: center middle # Lazily Evaluated --- class: center middle # Configuration File --- class: middle .center[ # Our Classes with DI ] ```perl package Foo; use Moo; has dbh => ( is => 'ro', required => 1, ); ``` ```perl package Bar; use Moo; has dbh => ( is => 'ro', required => 1, ); ``` --- class: middle .center[ # IOC ] ```perl use IOC; my $container = IOC::Container->new(); $container->register( IOC::Service->new('dbh' => sub { return DBI->connect( 'dbi:SQLite:data.db' ); }) ); $container->register( IOC::Service->new('foo' => sub { my $c = shift; return Foo->new( dbh => $c->get('dbh') ); }) ); $container->register( IOC::Service->new('bar' => sub { my $c = shift; return Bar->new( dbh => $c->get('dbh') ); }) ); ``` --- class: middle .center[ # Bread::Board ] ```perl use Bread::Board; my $c = container 'MyApp' => as { service dbh => ( lifecycle => 'Singleton', block => sub { require DBI; DBI->connect( 'dbi:SQLite:data.db' ); }, ); service foo => ( class => 'Foo', dependencies => { dbh => depends_on('dbh'), }, ); service bar => ( class => 'Bar', dependencies => { dbh => depends_on('dbh'), }, ); }; ``` --- class: middle .center[ # Beam::Wire ] ```perl # wire.yml dbh: class: DBI method: connect args: - 'dbi:SQLite:data.db' foo: class: Foo args: dbh: { $ref: dbh } bar: class: Bar args: dbh: { $ref: dbh } ``` ```perl use Beam::Wire; my $wire = Beam::Wire->new( file => 'wire.yml' ); ``` --- class: center middle # Why DI? --- class: center middle # Write Less Code! --- class: center middle # Don't Repeat Yourself! --- class: center middle # Develop to Interfaces --- class: middle .center[ # Roles as
(cheap)
Perl Interfaces ] ```perl package DAOInterface; requires qw( get set find delete ); ``` ```perl package Foo; use Moo; has dao => ( is => 'ro', isa => sub { $_[0]->does('DAOInterface'); }, ); ``` --- class: center middle # Loose Coupling --- class: center middle # More Re-use --- class: center middle # Easier Testing --- class: center middle # Less Boilerplate --- class: middle .center[ # BeamX::Service::AnyEvent
(upcoming)
] ```perl # wire.yml ping: class: BeamX::Service::AnyEvent::Ping slow_ping: class: BeamX::Service::AnyEvent::Ping args: interval: 5 ``` ```perl $ beam-ae wire.yml ping slow_ping ``` --- class: middle .center[ # BeamX::Service::MXRunnable
(upcoming)
] ```perl package PrintTable; use Moose; with 'MooseX::Runnable', 'MooseX::Getopt'; has dbh => ( is => 'ro', traits => [qw(NoGetopt)] ); has table => ( is => 'ro', ); sub run { my ( $self ) = @_; print $self->dbh->selectall_hashref( "SELECT * FROM " . $self->table ); } ``` --- class: middle .center[ # BeamX::Service::MXRunnable
(upcoming)
] ```perl # wire.yml dbh: class: DBI method: connect args: - 'dbi:SQLite:data.db' print_table: class: PrintTable args: dbh: { ref: dbh } ``` ```perl $ beam-mxr wire.yml print_table --table "my_table" ``` --- class: middle .center[ # Manually ] ```perl use Beam::Wire; use Moose; has container_file => ( is => 'ro', isa => 'Str', default => 'container.yml', ); has container => ( is => 'ro', isa => 'Beam::Wire', default => sub { my ( $self ) = @_; return Beam::Wire->new( file => $self->container_file ); }, ); sub execute { my ( $self, $opts, $args ) = @_; my $service_name = shift @$args; my $service = $self->container->get( $service_name ); $service->execute( $opts, $args ); } ``` --- class: center middle # Wrapping --- class: middle .center[ # BeamX::Service::Log4perl
(upcoming?)
] ```perl # wire.yml syslog_appender: class: BeamX::Service::Log4perl::Syslog args: host: 'localhost' port: 1514 email_appender: class: BeamX::Service::Log4perl::Email args: level: FATAL email: root@localhost logger: class: BeamX::Service::Log4perl args: appenders: - { ref: syslog_appender } - { ref: email_appender } ``` --- class: middle .center[ # BeamX::Service::Log4perl
(upcoming?)
] ```perl package LoggableRunner; use Moose; with 'MooseX::Runnable', 'MooseX::Getopt'; use Beam::Wire; has container_file => ( is => 'ro', isa => 'Str', default => 'wire.yml', ); has container => ( is => 'ro', isa => 'Beam::Wire', lazy => 1, default => sub { Beam::Wire->new( file => $_[0]->container_file ); }, ); ``` --- class: middle .center[ # BeamX::Service::Log4perl
(upcoming?)
] ```perl has logger_name => ( is => 'ro', isa => 'Str', default => 'logger', ); has logger => ( is => 'ro', lazy => 1, default => sub { my ( $self ) = @_; $self->container->get( $self->logger_name ); }, ); ``` --- class: middle .center[ # BeamX::Service::Log4perl
(upcoming?)
] ```perl sub BUILD { my ( $self ) = @_; $self->logger; # initialize logger } sub run { my ( $self ) = @_; # Do stuff, with logging! } ``` --- class: center middle # Voila! Integration --- class: center middle # Use Beam::Wire. Get Logging. --- class: center middle # You're already using DI! --- class: middle .center[ # Ha-ha! Fooled You! ] ```perl package Foo; use Moo; use DBI; has dbh => ( is => 'ro', default => sub { DBI->connect( 'dbi:SQLite:data.db' ); }, ); ``` ```perl package Bar; use Moo; use DBI; has dbh => ( is => 'ro', default => sub { DBI->connect( 'dbi:SQLite:data.db' ); }, ); ``` --- class: center middle # Use Inversion of Control! --- class: middle .center[ # It's Over! ] * Slides: [http://preaction.github.com/Perl/Dependency-Injection.html](http://preaction.github.com/Perl/Dependency-Injection.html) * Beam::Wire: [Beam::Wire on metacpan.org](http://metacpan.org/module/Beam::Wire)