Stevan Little
2004-12-27 21:04:40 UTC
So I have been using my module IOC a lot lately, so far so good, but I
have found that writing the code to create all the containers and
services and such gets quite tedious. For instance to do a basic DBI
container, I do something like this:
my $r = IOC::Registry->new('MyApp');
my $c = IOC::Container->new('DBI');
$c->register(IOC::Service::Literal->new('dsn' =>
'dbi:mysql:test'));
$c->register(IOC::Service::Literal->new('username' => 'test'));
$c->register(IOC::Service::Literal->new('password' => '****'));
$c->register(IOC::Service::ConstructorInjection->new('connection' => (
'DBI', 'connect' [
IOC::Service::ConstructorInjection->ComponentParameter('dsn'),
IOC::Service::ConstructorInjection->ComponentParameter('username'),
IOC::Service::ConstructorInjection->ComponentParameter('password')
]
)));
$r->registerContainer($c);
# ... then in my app all I have to do is
my $reg = IOC::Registry->instance();
my $db_conn = $reg->locateService('/MyApp/DBI/connection');
Writing the code to configure the services, containers and registry
though, can get quite tiring. Fortunately, I really only need to do
this once and then just use it, but ideally I would like it to be a
little less taxing on the carpal tunnel.
My first idea was to create subclasss of IOC::Container that would take
a number of arguments and return a fully formed container. I wrote a
basic IOC::Container::DBI which only needed to be passed a $dsn,
$username and $password and it would create what I have above. But that
idea only works for common things which you do all the time, not for
custom stuff.
Then it occurred to me that it would be even nicer to have some kind of
very basic configuration syntax/mini-language which would do this. So i
searched the Config:: namespace on CPAN and found nothing which seemed
to fit the bill.
So before I start really writing a custom parser and such I thought I
would get the opinion of others. Here is an example of how the above
configuration might look:
MyApp: [
DBI: [
dsn: 'dbi:mysql:test'
username: test
password: '****'
connection: [ DBI connect [ @dsn @username @password ] ]
]
]
This would roughly translate into the following perl data structure,
which I would then use to create the actual IOC objects:
{
'MyApp' => {
'DBI' => {
'dsn' => 'dbi:mysql:test',
'username' => 'test',
'password' => '****',
'connection' => [ 'DBI', 'connect', [ 'dsn', 'username', 'password'
]]
}
}
}
So as you can see in some cases, blocks ([]) will become hashes, and in
others they will become arrays. The way it knows is that if the first
value is a string with a colon at the end (:) it is a hash, otherwise
it is an array. The '@' prefix/sigil would indicate that the value is
not just a string, but a path or name of a component within the
IOC::Container.
I have worked out more complex examples as well, for instance this is
how IOC::Service::SetterInjection would be handled:
FileLogger: [
FlieLogger new [
[ set_log_file: @/MyApp/log_file_name ]
]]
this would become the following IOC configuration:
IOC::Service::SetterInjection->new('FileLogger' => (
'FileLogger', 'new', [
{ set_log_file => '/MyApp/log_file_name' }
]
)))
I have also devised a way to do the regular IOC::Service object, which
use the sub-ref parameter. Here is an example of the DBI configuration
above using this:
MyApp: [
DBI: [
dsn: 'dbi:mysql:test'
username: test
password: '****'
connection: <<<
my $c = shift;
return DBI->connect($c->get('dsn'), $c->get('username'),
$c->get('password'));
]
]
It would be like substituting this in the first config example:
$c->register(IOC::Service::->new('connection' => sub {
my $c = shift;
return DBI->connect($c->get('dsn'), $c->get('username'),
$c->get('password'));
}));
Obviously the string between '<<<' and '>>>' will get wrapped in 'sub {
}' and evaled. I am assuming that this will work since '<<<' and '>>>'
are not valid perl identifiers, so I can easily find the end of that
string.
As I said, I am still toying around with this. I have created a quick
and dirty parser already so I know the syntax is workable for that
angle. My question to you all, is;
1) Does this sound like a good idea?
2) Does this syntax look good?
3) Is there any existing Config:: module out there which will do the
dirty work for me?
4) Anyone got any better ideas?
Thanks,
Steve
have found that writing the code to create all the containers and
services and such gets quite tedious. For instance to do a basic DBI
container, I do something like this:
my $r = IOC::Registry->new('MyApp');
my $c = IOC::Container->new('DBI');
$c->register(IOC::Service::Literal->new('dsn' =>
'dbi:mysql:test'));
$c->register(IOC::Service::Literal->new('username' => 'test'));
$c->register(IOC::Service::Literal->new('password' => '****'));
$c->register(IOC::Service::ConstructorInjection->new('connection' => (
'DBI', 'connect' [
IOC::Service::ConstructorInjection->ComponentParameter('dsn'),
IOC::Service::ConstructorInjection->ComponentParameter('username'),
IOC::Service::ConstructorInjection->ComponentParameter('password')
]
)));
$r->registerContainer($c);
# ... then in my app all I have to do is
my $reg = IOC::Registry->instance();
my $db_conn = $reg->locateService('/MyApp/DBI/connection');
Writing the code to configure the services, containers and registry
though, can get quite tiring. Fortunately, I really only need to do
this once and then just use it, but ideally I would like it to be a
little less taxing on the carpal tunnel.
My first idea was to create subclasss of IOC::Container that would take
a number of arguments and return a fully formed container. I wrote a
basic IOC::Container::DBI which only needed to be passed a $dsn,
$username and $password and it would create what I have above. But that
idea only works for common things which you do all the time, not for
custom stuff.
Then it occurred to me that it would be even nicer to have some kind of
very basic configuration syntax/mini-language which would do this. So i
searched the Config:: namespace on CPAN and found nothing which seemed
to fit the bill.
So before I start really writing a custom parser and such I thought I
would get the opinion of others. Here is an example of how the above
configuration might look:
MyApp: [
DBI: [
dsn: 'dbi:mysql:test'
username: test
password: '****'
connection: [ DBI connect [ @dsn @username @password ] ]
]
]
This would roughly translate into the following perl data structure,
which I would then use to create the actual IOC objects:
{
'MyApp' => {
'DBI' => {
'dsn' => 'dbi:mysql:test',
'username' => 'test',
'password' => '****',
'connection' => [ 'DBI', 'connect', [ 'dsn', 'username', 'password'
]]
}
}
}
So as you can see in some cases, blocks ([]) will become hashes, and in
others they will become arrays. The way it knows is that if the first
value is a string with a colon at the end (:) it is a hash, otherwise
it is an array. The '@' prefix/sigil would indicate that the value is
not just a string, but a path or name of a component within the
IOC::Container.
I have worked out more complex examples as well, for instance this is
how IOC::Service::SetterInjection would be handled:
FileLogger: [
FlieLogger new [
[ set_log_file: @/MyApp/log_file_name ]
]]
this would become the following IOC configuration:
IOC::Service::SetterInjection->new('FileLogger' => (
'FileLogger', 'new', [
{ set_log_file => '/MyApp/log_file_name' }
]
)))
I have also devised a way to do the regular IOC::Service object, which
use the sub-ref parameter. Here is an example of the DBI configuration
above using this:
MyApp: [
DBI: [
dsn: 'dbi:mysql:test'
username: test
password: '****'
connection: <<<
my $c = shift;
return DBI->connect($c->get('dsn'), $c->get('username'),
$c->get('password'));
]
]
It would be like substituting this in the first config example:
$c->register(IOC::Service::->new('connection' => sub {
my $c = shift;
return DBI->connect($c->get('dsn'), $c->get('username'),
$c->get('password'));
}));
Obviously the string between '<<<' and '>>>' will get wrapped in 'sub {
}' and evaled. I am assuming that this will work since '<<<' and '>>>'
are not valid perl identifiers, so I can easily find the end of that
string.
As I said, I am still toying around with this. I have created a quick
and dirty parser already so I know the syntax is workable for that
angle. My question to you all, is;
1) Does this sound like a good idea?
2) Does this syntax look good?
3) Is there any existing Config:: module out there which will do the
dirty work for me?
4) Anyone got any better ideas?
Thanks,
Steve