stevan / breadboard Goto Github PK
View Code? Open in Web Editor NEWInversion of Control and Dependency Injection for Perl
Inversion of Control and Dependency Injection for Perl
We have a number of modules that extend the Bread::Board DSL by doing:
Moose::Exporter->setup_import_methods(
...
also => 'Bread::Board',
);
If there's not a compelling reason for #53, can you please revert it?
Thanks!
I managed to successfully run dzil authordeps --missing | cpanm
on my Perl 5.18.4 perlbrew and somehow got into a weird state.
$ dzil authordeps --missing
[... no output ...]
But then...
$ dzil listdeps
Required plugin Dist::Zilla::Plugin::Test::ReportPrereqs isn't installed.
Run 'dzil authordeps' to see a list of all required plugins.
You can pipe the list to your CPAN client to install or update them:
dzil authordeps --missing | cpanm
Any ideas what's up with that?
On top of that, if I do install Dist::Zilla::Plugin::Test::ReportPrereqs manually things don't get any better.
$ cpanm Dist::Zilla::Plugin::Test::ReportPrereqs
[...]
1 distribution installed
But then...
$ dzil listdeps
fatal: ambiguous argument 'releases...master': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]' at /home/alex/perl5/perlbrew/perls/perl-5.18.4/lib/site_perl/5.18.4/Dist/Zilla/Plugin/ChangeStats/Git.pm line 61.
Any ideas? If I can't get the dependencies installed and dzil running then working on #47 is going to be tricky.
Thanks,
Alex
If your service has a lifecycle applied, you can remove it by setting $service->lifecycle('Null')
. This feels wrong (also, the way we remove a role by reblessing into the first superclass is probably fragile, but that's for another day).
Should I write a (essentially empty) Bread::Board::Lifecycle::Prototype
(to use the name from the manual) and document it? That way we can remove the special-cased name.
ConstructorInjection
uses the MOP (plus override) to find the constructor to call, but SetterInjection
always calls new
. Should I factor out the constructor_name
attribute and use it in both injections?
I was working on some code recently where I had a container for my test code that could be used to create a temporary database for fixtures. The database should be removed at the end of each test run, or else I end up with a million databases named "Foo_12345".
However, it's possible some tests would never resolve the fixtures service, so the database would never be created. I tried to get the database object to clean up after itself on DEMOLISH
, but the unpredictability of Perl's global destruction order make this really difficult (or plain impossible).
I have a test runner that wraps my TCM classes that is perfectly positioned to do this, though. It can run the tests and then check if the container resolved the fixtures. If so, it disconnects the database handle (also made by the container), then drops the test database.
I made this all work by just wrapping the Bread::Board::Container
in my own object that has its own resolve
method that just tracks what strings it sees. It'd be a lot nicer if it knew that '/foo' and 'foo' were equivalent services though, which is why I'd like to see this in BB itself. Something like a $container->resolved_service
or ->service_was_resolved
or something like that.
The last bit simply cannot work, because of the way that a container is named when generated from parameters:
my $ld = $c->resolve( service => 'Logging/Logger' );
This will cause an error - "Could not find container or service for Logging in Logging|Outputs at lib/Bread/Board/Traversable.pm line 104."
That's cause the name of the container is Logging|Outputs
, not Logging
.
Obviously, fixing this is easy, but I find the container naming a little gross. Is it possible to fix that so it doesn't include the parameter names? I can't see a good reason to include those in the name, but I don't know enough about Bread::Board
to say for sure.
I'm not a fan of Bread::Board::Declare, but I've often found myself wanting something like dep(value => ...)
when using Bread::Board. It would be nice to be able to quickly specify anonymous literals to service dependencies.
Would it be possible to add a literal()
or value()
function to Bread::Board to create anonymous Bread::Board::Literal services?
Those modules are missing an ABSTRACT line like:
package Foo;
# ABSTRACT: does the thing
Hi there,
This month I got BreadBoard as my CPAN Pull Request Challenge. I know you already know about it because I already did a CPAN PR Challenge for one of your distributions in January :-)
It looks like there's a couple of issues and pull requests open on Github. Any in particular that you'd like me to take a look at?
Alex
I have this setup:
package Foo;
use Moo;
has p1 => ( is => 'rw' );
has p2 => ( is => 'rw' );
has p3 => ( is => 'rw' );
has p4 => ( is => 'rw' );
package main;
my $c = container 'container' => as {
service 'foo' => (
class => 'Foo',
parameters => {
p1 => { required => 1 },
p2 => { required => 0 },
p3 => { },
p4 => { },
}
);
};
my $foo = $c->resolve(service => 'foo', parameters => { p4 => 'test' });
I get this error:
Mandatory parameters 'p1', 'p2', 'p3' missing in call to Bread::Board::Service::WithParameters::check_parameters.
Why is p2
required? It has required => 0
.
What I'm seeing is a parent ending up containing both the parameterized container and the container it generates (the FromParameterized
one).
I tried to boil this down to the simplest example I could:
package Foo;
use v5.22;
use Bread::Board;
use Moose;
use feature 'signatures';
no warnings 'experimental::signatures';
has broken => (
is => 'ro',
lazy => 1,
builder => '_build_broken',
);
has works => (
is => 'ro',
lazy => 1,
builder => '_build_works',
);
has _db_name => (
is => 'ro',
lazy => 1,
builder => '_build_db_name',
);
has _database_container => (
is => 'ro',
lazy => 1,
builder => '_build_database_container',
);
sub _build_db_name {'Foo'}
sub _build_broken ($self) {
my $db_name = 'Foo';
my $c = container db_name => as {
service name => $db_name;
};
return container master => as {
container(
$self->_database_container->create(
db_name => $c,
)
);
};
}
sub _build_works ($self) {
my $db_name = $self->_db_name;
my $c = container db_name => as {
service name => $db_name;
};
my $db = $self->_database_container->create(
db_name => $c,
);
return container master => as {
container($db);
};
}
sub _build_database_container ($self) {
return container database => ['db_name'] => as {
service name => {
block => sub ($s) {
$s->parent->get_sub_container('db_name')
->resolve( service => 'name' );
}
};
};
}
__PACKAGE__->meta->make_immutable;
package main;
use Bread::Board::Dumper;
say 'Broken';
say '-------';
say Bread::Board::Dumper->new->dump( Foo->new->broken );
say 'Works';
say '-------';
say Bread::Board::Dumper->new->dump( Foo->new->works );
Note that I tried removing the Moose bits and doing this with non-method subs, and I couldn't get the issue to recur.
I think the problem has to do with this particular bit of code:
return container master => as {
container(
$self->_database_container->create(
db_name => $c,
)
);
};
I think what's happening is that the first container master
bit sets up a contact in Bread::Board
causing it to set $CC
. Then the subsequent call to container()
in _build_database_container
ends up adding this newly created parameterized container to the root, even though that's not what I wanted.
I have no idea how to fix this. If it can't be fixed, it's probably worth documenting.
That said, I tried to make a much simpler example using plain subs and couldn't replicate this. I really don't know why!
Found a bug where:
We can work around this easily enough by wrapping the contents of the block with eval, but I actually liked the fact that the exception was being propagated to whoever called $container->resolve. If there's a way to keep that behavior, while still cleaning up the dependencies, that would be great.
Why can't we defer a parameterized service, if we have the parameters?
In ::WithDependencies::resolve_dependencies
, could we not stash the $dependency->service_params
alongside the $service
object in the ::Deferred
proxy? There's probably a good reason, but I don't see itβ¦
Also, why is the is_locked
attribute not marked NoClone
?
BB::Lifecycle::Singleton
uses a lock while get
ing, but BB::Lifecycle::Singleton::WithParameters
does not. Why? Should it? Should the lock be on the whole service, or on the specific get
call (i.e. should we have a single boolean lock, or a hashref from parameters keys to booleans)?
Is it intended that parameterized containers rename themselves to include all the parameter names? For example, given this snippet:
my $pc = container X => [ qw( Y Z ) ] => as { ... }
I would expect the following test to pass:
use Test::More;
my $c = $pc->create( Y => $yc, Z => $zc );
is($c->name, 'X', 'container is named X');
However, the passing test is actually:
is($c->name, 'X|Y|Z', 'container is named X with parameters');
This means to recover the expected name, I have to call the name
setter to adjust the container. This isn't a problem, I just wanted to know if this is the intended behavior and can provide a documentation patch to Bread::Board::Manual::Concepts::Advanced.
I have a project where it would be nice to have a set of services that act as a sort of template for another set of services. For example, I could define a generic database connector service that might be used to help construct a whole bunch of real database connectors. I was contemplating the benefits of doing this through Bread::Board like this:
container ResourceTypes => as {
container DB => as {
service connection => (
class => 'MyApp::DB::Handle',
parameters => {
host => { isa => 'Str' },
port => { isa => 'Int' },
schema => { isa => 'Str' },
user => { isa => 'Str' },
password => { isa => 'Str' },
},
};
};
Later, I define a set of actual configurations which can be loaded something like this:
container Resources => [ 'ResourceType' ] => as {
service devdb_host => 'devdb';
service devdb_port => 3306;
service devdb_schema => 'myapp';
service devdb_user => 'sandbox';
service devdb_password => 'secret';
service devdb => (
service_type => 'Service',
service_path => 'ResourceType/DB/connection',
dependencies => {
host => depends_on('devdb_host'),
port => depends_on('devdb_port'),
schema => depends_on('devdb_schema'),
user => depends_on('devdb_user'),
password => depends_on('devdb_password'),
},
);
};
The ServiceInjection
class would simply do a fetch()
on the named service_path
and pass through the parameters setup by the dependencies
and parameters
, allowing one service to be built up from another.
This works as a sort of currying, I guess, allowing one service to be built upon another.
Thoughts? If that sounds like a reasonable idea, I can send a pull request. Otherwise, I may just find a different way because I don't really need to create this just for me.
it's kind of annoying to require strict validation to an object that is capable of validating itself. e.g. instantiating a model which has a lot of parameters, but needs a few dependencies injected. could we add the following to
package Bread::Board::Service::WithParameters;
around line 62
MX_PARAMS_VALIDATE_ALLOW_EXTRA => 1,
I see 2 options for doing this, option A, just add it, anyone who's code conforms to strict will still work. Option B, add a new attribute allow_extra
which will set this if it's set. I prefer option A, because I think it makes Bread::Board easier to use in general, but I could understand that it could be seen as a behavior change.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.