Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Subclassing DBI instead of wrapping

by spq (Friar)
on Aug 22, 2006 at 16:24 UTC ( #568901=perlquestion: print w/ replies, xml ) Need Help??
spq has asked for the wisdom of the Perl Monks concerning the following question:

I wrote my first module wrapping DBI years ago, and have used it on a few projects. Basically I've done this for two general objectives. The first is to wrap the DBI->connect call with a connect of my own, using a different name (such as db_connect). The purpose of custom connect isn't to muddle with DBI's internal connection mechanics, but just to handle things like providing some default values and/or retry connecting a few times. Inside my connect method I use DBI->connect and then store the $dbh as an attribute in the the object I bless into my own class and then return. The second reason is to provide a few special methods which all operate on a database. In no place am I trying to override existing DBI methods, just add new ones.

So far I have accomplished this fine by writing my custom DB class as a wrapper. In my custom methods I would use $self->{DBH}->prepare for example, and I use AUTOLOAD to simply pass any call not recognized in my module on to DBI:

sub AUTOLOAD { my $self = shift; my $call = $AUTOLOAD; $call =~ s/.*:://; return $self->{DBH}->$call(@_); } # AUTOLOAD

But I find myself in the middle of a moderate rewrite of an old project too long neglected. While the rest of the modules in the project are becoming more carefully designed classes, I feel an urge to do the same with my custom DBI wrapper, making it instead a subclass of DBI. This would allow me drop AUTOLOAD, but how would it effect my custom methods? If I read the docs correctly, $self->{DBH}->prepare() would just become $self->prepare(); OK, minor improvement.

Which leaves making the database connection, and this is where I found the docs (or mostly the ancillary articles on the subject) a bit confusing, giving some dire words of caution. ... Can I continue to use my own connect method, leaving DBI's available to the user if needed, and just replace $self{DBH} = DBI->connect(...) with $dbh = DBI->connect(..., { RootClass => 'MySubDBI' }) and return that connection handle instead of blessing it myself? Would it be more prudent to override DBI's connect method, calling SUPER::connect internally?

So fellow monks, besides hoping someone with experience could provide advice or sample snippets clarifying my questions above, I humbly ask you: Are the gains of sublassing DBI over wrapping it worth the effort given my simple needs?

Thanks!

Comment on Subclassing DBI instead of wrapping
Select or Download Code
Re: Subclassing DBI instead of wrapping
by samtregar (Abbot) on Aug 22, 2006 at 17:33 UTC
    Are the gains of sublassing DBI over wrapping it worth the effort given my simple needs?

    I doubt it. DBI's internal structure makes sub-classing quite challenging. I would sub-class DBI only when every other reasonable option is ruled out.

    What do you want to override aside from connect()? If that's it then I'd suggest you not bother with AUTOLOAD, just return the DBI object itself! This has worked well on several large projects, see Krang's Krang::DB class for an example.

    -sam

Re: Subclassing DBI instead of wrapping
by jeffa (Chancellor) on Aug 22, 2006 at 17:45 UTC

    If you are only wanting to do this because you don't think your original wrapper is as carefully designed as the rest of your project -- AND you will only be changing the internals and not the API -- then no, i don't see very much value in changing it.

    Aside from that, i also see very little value in writing a wrapper or subclassing DBI in the first place.* There are helper modules out there to handle config data, and i have never needed to retry connecting to the database myself. YMMV of course, but i like DBI straight out of the box.

    * update: renodino explains this point better than i did. renodino++

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
Re: Subclassing DBI instead of wrapping
by renodino (Curate) on Aug 22, 2006 at 18:52 UTC
    While I disagree with earlier assertions wrt the difficulty in subclassing DBI (except possibly for some conceptual challenges), you haven't really presented what your purpose is other than "adding a few methods".

    Where I've found subclassing useful is for extending SQL, rather than the DBI API (e.g., DBIx::Chart). Similarly, it might be useful for applying filters for calls to the existing API (e.g., scrubbing parameters and/or results, implementing a federated database abstraction, etc.). However, it isn't amenable to some things (e.g., DBIx::Threaded required an entire wrapper, even tho it was just implementing the same API)

    I think the dividing line may be "extending DBI so any existing DBI app can use it" vs. adding new APIs to DBI...the latter will likely require apps to make non-trivial changes anyway, so the ROI of subclassing may not be that great.

    However... if O-R wrapper authors accomodated DBI subclass support, then it might be worth the effort of subclassing, and perhaps reconsidering your enhancement as a SQL syntax filter or do()/prepare()/execute() attributes addition ?

      What I mean by "adding a few methods" is really just laziness. And in all cases I've done this it was for very topic-specific environments. For example, one use I've made is at a bioinformatics center where we have a database of genomic information. While many projects will have their own databases, there are certain very common requests which might be made from the public databases. For instance, getting all the peptide sequences for a list of given ids. While it's a simple enough SQL statement to write, it is used so often I wanted to provide an easier way to accomplish this frequent task. Originally I had a module which had a dozen or so similar shortcuts that took a database handle as one of the arguments (this also insulated the database design allowing me to improve and extend the schema with vastly less code breakage). However I also noticed I was pasting the same connection method in most of my code (same database, server, username, etc) and so I added a method to connect to that module. I'd barely done coding that and changing some programs to use it before it occurred to me that calling a function in my module to connect to the database, then handing that connection right back to other functions in the module was a bit of a waste of time.

      So, I sorted out how to use AUTOLOAD (this was going on 6 years ago) and instead just wrapped the DBI. This made a lot of things much simpler; any code using my module could use the database handle returned just as normal, but they also got a small collection of simple, stable short-cuts for doing some very common tasks. I've found the template so useful I have reused it in a number of other similar projects. Nothing fancy, and as I said, nothing whatsoever that interferes with how any of DBI's built-in methods work.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://568901]
Approved by friedo
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (12)
As of 2014-12-29 16:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (193 votes), past polls