Re: Fwd: Idea for a Gofer transport for translating SQL - primarily intended for testing
Andrew Ford <[email protected]>
| Newsgroups | gmane.comp.lang.perl.modules.dbi.general |
|---|---|
| Message-ID | <[email protected]> |
Thinking about how middleware could be implemented in DBD::Gofer - see
below ...
Tim Bunce wrote:
> On Thu, Sep 08, 2011 at 01:19:17PM +0100, Andrew Ford wrote:
>
>> and I would quite like to get this module onto CPAN and would like some feedback as to
>> whether "the expert" thinks it is a bad idea. I think it would be a useful addition to the testing
>> armoury, but then I am obviously biased.
>>
>
> The general idea is certainly useful. I'd like to generalise it though.
>
> There's a conceptual similarity with PSGI/Plack middlewares that I'd
> like to build on. Specifically, rather than creating a new transport,
> I'd like to see DBD/Gofer/Transport/Base.pm extended to call one or more
> 'Gofer client middlewares' just before it calls transmit_request_by_transport().
>
> The calling code could be something like:
>
> + my $mw = $self->middleware;
> my $transmit_sub = sub {
> my $response = eval {
> ...
> - $self->transmit_request_by_transport($request)
> + $mw->($self, $request);
> }
> ...
> }
>
> The middleware attribute would default to:
>
> sub { shift->transmit_request_by_transport(@_) }
>
> new middleware layer would be added by doing:
>
> my $mw = $self->middleware;
> $self->middleware( sub {
> my ($self, $request) = @_;
> ...do something with $request...
> my $response = $mw->($self, $request);
> ...do something with $response...
> return $response;
> } );
>
> [This is all off-the-top-of-my-head, and I'm not very familar with Plack
> internals so I may well be missing important issues.]
>
> Then we just need a way to add middlewares via the environment.
>
> I'd be delighted if you would work up a patch to add that to the DBI.
>
Currently DBD::Gofer::dr:connect() contains code to set up the transport:
my $transport_class = delete $go_attr{go_transport}
or return $drh->set_err($DBI::stderr, "No transport=
argument in '$orig_dsn'");
$transport_class = "DBD::Gofer::Transport::$transport_class"
unless $transport_class =~ /::/;
_load_class($transport_class)
or return $drh->set_err($DBI::stderr, "Can't load
$transport_class: $@");
my $go_transport = eval { $transport_class->new(\%go_attr) }
or return $drh->set_err($DBI::stderr, "Can't instanciate
$transport_class: $@");
I would imagine that we could have a "middleware" keyword that was a
comma-separated list of names that were assumed to be in the
DBDx::GoferMiddleware namespace - these would be installed in sequence
with a similar block of code that follows on from the snippet above,
looking something like:
my $middleware_classes = delete $go_attr{go_middleware};
foreach my $middleware_class (split /,/,
$middleware_classes) {
$middleware_class =
"DBDx::GoferMiddleware::$middleware_class"
unless $middleware_class =~ /::/;
_load_class($middleware_class)
or return $drh->set_err($DBI::stderr, "Can't load
$middleware_class: $@");
$go_transport->middleware( sub { return
$middleware_class->HANDLER(shift, $go_transport->middleware) } );
}
Each middleware handler would take a request object and a reference to
the next handler in the chain.
We would need to have a default method name for the middleware handler
(I've used the placeholder HANDLER in the pseudo-code above).
The DSNs would then look like:
dbi:Gofer:transport=null;middleware=adaptor;transform=MyMapper;dsn=dbi:XXX...
There are a few holes in this that I can think of:
* We might want to instantiate a middleware object for each
component and use that in installing the middleware handler
* I am not sure how we could specify a handler name other than the
default for individual middleware components
* Also not sure how attributes for each middleware component could
be specified such that the component for which the attribute is
intended could be specified - the example DSN above assumes that
the attribute (transform in the example DSN) is just dumped into
the general "go_" attribute hash)
* The go_middleware attribute could be set in the DBI connect
attribute hash to a list of subroutine references (code would need
to differentiate between a comma-separated list value and a list
reference value)
* Should the middleware components be listed innermost first (as
implemented in the pseudo-code above) or outermost first?
* We might want to add some convenience methods to the request class
to build response objects (as suggested in my
DBD::Gofer::Transport::adaptor code)
I'll have a go at getting this coded over the next few days, but it
looks as if the changes required to implement middleware are relatively
trivial.
> Your module could then use this mechanism. If you wanted to release it
> to CPAN then a name like DBDx::GoferMiddleware::FOO would be good.
> (Note the DBDx not DBIx since this is client-side. We may well end up
> with server-side gofer middlewares as well.)
>
I think I would take the name DBDx::GoferMiddleware::adaptor for my
transformation middleware, unless anyone has a better idea (are we
keeping the final class name component for the middleware class in lower
case, as it is with the transport classes?).
Andrew