NAME
Beam::Wire - A Dependency Injection Container
VERSION
version 1.003
SYNOPSIS
# wire.yml
dbh:
class: 'DBI'
method: connect
args:
- 'dbi:mysql:dbname'
- {
PrintError: 1
}
# myscript.pl
use Beam::Wire;
my $wire = Beam::Wire->new( file => 'wire.yml' );
my $dbh = $wire->get( 'dbh' );
$wire->set( 'dbh' => DBI->new( 'dbi:pgsql:dbname' ) );
DESCRIPTION
Beam::Wire is a dependency injection (DI) container. A DI (dependency
injection) container is a framework/mechanism where dependency creation
and instantiation is handled automatically (e.g. creates instances of
classes that implement a given dependency interface on request). DI does
not require a container, in-fact, DI without a container is possible and
simply infers that dependency creation isn't automatically handled for
you (i.e. you have to write code to instantiate the dependencies
manually).
Dependency injection (DI) at it's core is about creating loosely coupled
code by separating construction logic from application logic. This is
done by pushing the creation of services (dependencies) to the entry
point(s) and writing the application logic so that dependencies are
provided for its components. The application logic doesn't know or care
how it is supplied with its dependencies; it just requires them and
therefore receives them.
OVERVIEW
Beam::Wire loads a configuration file and stores the specified
configuration in the config which is used to resolve it's services. This
section will give you an overview of how to declare dependencies and
services, and shape your configuration file.
WHAT IS A DEPENDENCY?
A dependency is a declaration of a component requirement. In layman's
terms, a dependency is a class attribute (or any value required for
class construction) which will likely be used to define services.
WHAT IS A SERVICE?
A service is a resolvable interface which may be selected and
implemented on behalf of a dependent component, or instantiated and
returned per request. In layman's terms, a service is a class
configuration which can be used independently or as a dependent of other
services.
HOW ARE SERVICES CONFIGURED?
# databases.yml
production_db:
class: 'DBI'
method: connect
args:
- 'dbi:mysql:master'
- { PrintError: 0, RaiseError: 0 }
production_cache:
class: 'CHI'
args:
driver: 'DBI'
dbh: { $ref: 'production_db' }
development_db:
class: 'DBI'
method: connect
args:
- 'dbi:mysql:slave'
- { PrintError: 1, RaiseError: 1 }
development_cache:
class: 'CHI'
args:
driver: 'DBI'
dbh: { $ref: 'development_db' }
Service Attributes
class
The class to instantiate. The class will be loaded and the "method"
(below) method called.
method
The class method to call to construct the object. Defaults to "new".
If multiple methods are needed to initialize an object, "method" can be
an arrayref of hashrefs, like so:
my_service:
class: My::Service
method:
- method: new
args:
foo: bar
- method: set_baz
args:
- Fizz
In this example, first we call "My::Service-"new( foo => "bar" );> to
get our object, then we call "$obj-"set_baz( "Fizz" );> as a further
initialization step.
To chain methods together, add "return: chain":
my_service:
class: My::Service
method:
- method: new
args:
foo: bar
- method: set_baz
return: chain
args:
- Fizz
- method: set_buzz
return: chain
args:
- Bork
This example is equivalent to the following code:
my $service = My::Service->new( foo => "bar" )->set_baz( "Fizz" )
->set_buzz( "Bork" );
args
The arguments to the "method" method. This can be either an array or a
hash, like so:
# array
dbh:
class: DBI
method: connect
args:
- 'dbi:mysql:dbname'
# hash
cache:
class: CHI
args:
driver: Memory
max_size: 16MB
Using the array of arguments, you can give arrayrefs or hashrefs:
# arrayref of arrayrefs
names:
class: 'Set::CrossProduct'
args:
-
- [ 'Foo', 'Barkowictz' ]
- [ 'Bar', 'Foosmith' ]
- [ 'Baz', 'Bazleton' ]
# arrayrefs of hashrefs
cache:
class: CHI
args:
- driver: Memory
max_size: 16MB
extends
Inherit and override attributes from another service.
dbh:
class: DBI
method: connect
args:
- 'dbi:mysql:dbname'
dbh_dev:
extends: 'dbh'
args:
- 'dbi:mysql:devdb'
Hash "args" will be merged seperately, like so:
activemq:
class: My::ActiveMQ
args:
host: example.com
port: 61312
user: root
password: 12345
activemq_dev:
extends: 'activemq'
args:
host: dev.example.com
"activemq_dev" will get the "port", "user", and "password" arguments
from the base service "activemq".
lifecycle
Control how your service is created. The default value, "singleton",
will cache the resulting service and return it for every call to
"get()". The other value, "factory", will create a new instance of the
service every time:
today:
class: DateTime
method: today
lifecycle: factory
args:
time_zone: US/Chicago
report_yesterday:
class: My::Report
args:
date: { $ref: today, $method: add, $args: [ "days", "-1" ] }
report_today:
class: My::Report
args:
date: { $ref: today }
"DateTime-"add> modifies the object and returns the newly-modified
object (to allow for method chaining.) Without "lifecycle: factory", the
"today" service would become yesterday, making it hard to know what
"report_today" would report on.
An "eager" value will be created as soon as the container is created. If
you have an object that registers itself upon instantiation, you can
make sure your object is created as soon as possible by doing
"lifecycle: eager".
Inner Containers
Beam::Wire objects can hold other Beam::Wire objects!
inner:
class: Beam::Wire
args:
config:
dbh:
class: DBI
method: connect
args:
- 'dbi:mysql:dbname'
cache:
class: CHI
args:
driver: Memory
max_size: 16MB
Inner containers' contents can be reached from outer containers by
separating the names with a slash character:
my $dbh = $wire->get( 'inner/dbh' );
Inner Files
inner:
class: Beam::Wire
args:
file: inner.yml
Inner containers can be created by reading files just like the main
container. If the "file" attribute is relative, the parent's "dir"
attribute will be added:
# share/parent.yml
inner:
class: Beam::Wire
args:
file: inner.yml
# share/inner.yml
dbh:
class: DBI
method: connect
args:
- 'dbi:sqlite:data.db'
# myscript.pl
use Beam::Wire;
my $container = Beam::Wire->new(
file => 'share/parent.yml',
);
my $dbh = $container->get( 'inner/dbh' );
If more control is needed, you can set the dir on the parent container.
If even more control is needed, you can make a subclass of Beam::Wire.
Service/Configuration References
chi:
class: CHI
args:
driver: 'DBI'
dbh: { $ref: 'dbh' }
dbh:
class: DBI
method: connect
args:
- { $ref: dsn }
- { $ref: usr }
- { $ref: pwd }
dsn:
value: "dbi:SQLite:memory:"
usr:
value: "admin"
pwd:
value: "s3cret"
The reuse of service and configuration containers as arguments for other
services is encouraged so we have provided a means of referencing those
objects within your configuration. A reference is an arugment (a service
argument) in the form of a hashref with a $ref key whose value is the
name of another service. Optionally, this hashref may contain a $path
key whose value is a Data::DPath search string which should return the
found data structure from within the referenced service.
It is also possible to use raw-values as services, this is done by
configuring a service using a single key/value pair with a "value" key
whose value contains the raw-value you wish to reuse.
ATTRIBUTES
file
The file attribute contains the file path of the file where Beam::Wire
container services are configured (typically a YAML file). The file's
contents should form a single hashref. The keys will become the service
names.
dir
The dir attribute contains the directory path to use when searching for
inner container files. Defaults to the directory which contains the file
specified by the file.
config
The config attribute contains a hashref of service configurations. This
data is loaded by Config::Any using the file specified by the file.
services
A hashref of services. If you have any services already built, add them
here.
meta_prefix
The character that begins a meta-property inside of a service's "args".
This includes $ref, $path, $method, and etc...
The default value is '$'. The empty string is allowed.
METHODS
get( name, [ overrides ] )
The get method resolves and returns the service named "name".
"overrides" may be a list of name-value pairs. If specified, get() will
create an anonymous service that extends the "name" service with the
given config overrides:
# test.pl
use Beam::Wire;
my $wire = Beam::Wire->new(
config => {
foo => {
args => {
text => 'Hello, World!',
},
},
},
);
my $foo = $wire->get( 'foo', args => { text => 'Hello, Chicago!' } );
print $foo; # prints "Hello, Chicago!"
This allows you to create factories out of any service, overriding
service configuration at run-time.
set
The set method configures and stores the specified service.
get_config
Get the config with the given name, searching inner containers if
required
new
Create a new container.
EXCEPTIONS
If there is an error internal to Beam::Wire, an exception will be
thrown. If there is an error with creating a service or calling a
method, the exception thrown will be passed- through unaltered.
Beam::Wire::Exception
The base exception class
Beam::Wire::Exception::Service
An exception with service information inside
Beam::Wire::Exception::NotFound
The requested service or configuration was not found.
Beam::Wire::Exception::InvalidConfig
The configuration is invalid:
* Both "value" and "class" or "extends" are defined. These are
mutually-exclusive.
AUTHORS
* Doug Bell <preaction@cpan.org>
* Al Newkirk <anewkirk@ana.io>
COPYRIGHT AND LICENSE
This software is copyright (c) 2013 by Doug Bell.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.