From: Tim Bunce Date: Wed Jan 02, 2002 08:45:59 PM US/Eastern To: dbi-dev@perl.org Subject: (Fwd) Important: Subclassing and Merging DBIx::AnyDBD into the DBI FYI - This has just been sent to dbi-users@perl.org. Probably best to discuss it there in whatever threads it spawns (but do post here if you've a driver-development related question or you're not subscribed to dbi-users) Tim. ----- Forwarded message from Tim Bunce ----- Delivered-To: tim.bunce@pobox.com Date: Thu, 3 Jan 2002 01:25:03 +0000 From: Tim Bunce To: dbi-users@perl.org Cc: Matt Sergeant , Tim Bunce Subject: Important: Subclassing and Merging DBIx::AnyDBD into the DBI Here's what I'm thinking, and developing, at the moment... [Please read and think about it all before commenting] [Also, I've CC'd Matt Sergeant , please ensure that he's CC'd on any replies. Thanks.] Firstly, subclassing... (we'll use MyDBI as the example subclass here) The "mixed case with leading capital letter" method namespace will be reserved for use by subclassing of the DBI. The DBI will never have any methods of it's own in that namespace. (DBI method names are either all lower case or all upper case.) The need to call MyDBI->init_rootclass will be removed. Simply calling $dbh = MyDBI->connect(...) will be interpreted as a request to have the $dbh blessed into the MyDBI::db class (and a $dbh->prepare will return $sth blessed into MyDBI::st). A warning will be generated if @MyDBI::db::ISA is empty. Also, and this is where it gets interesting, calling: DBI->connect(,,, { RootClass => 'MyDBI' }) will have the same effect as above, with the added feature that the DBI will try to automatically load MyDBI.pm for you. It'll ignore a failure to load due to the file not existing if the MyDBI class already exists. This feature dramatically opens up the scope of DBI subclassing. The idea behind it is that the $dbh object is no longer 'just' encapsulating a simple database connection, it can now encapsulate a high-level information repository that can be 'queried' at a more abstract level. So instead of just calling low-level do/prepare/execute/fetch methods you can now call higher-level methods that relate to your own data and concepts. More below. Typically a 'Sales Order Handling' database could now be given a SalesOrderDBI::db class containing high-level methods that deal directly with Sales Order Handling concepts and could do things like automatically trigger re-ordering when stocks get low. Also consider, for example, that DBD::Proxy would be able to dynamically load the subclass on the proxy server instead of the proxy client. The subclass can perform multiple DBI method calls before returning a result to the client. For example: $ok=$dbh->Check_Available($a,$b) on the proxy client triggers a $dbh->Check_Available($a,$b) call on the proxy server and that method may perform many selects to gather the info before returning the boolean result to the client. Performing the selects on the proxy server is far far more efficient. In terms of buzzwords, the dynamic loading of subclasses can translate into "Encapsulating Business Logic" and thowing in the proxy extends that to "3-Tier" :) Also, the ability to embed attributes into the DSN may lead to some interesting possibilities... DBI->connect("dbi:Oracle(PrintError=1,RootClass=OtherDBI):...",...) I'm not sure where that road leads but I suspect it'll be interesting, though I may disable it by default, or just provide a way to do so. Next, merging in DBIx::AnyDBD functionality... Rather than describe Matt Sergeant's excellent DBIx::AnyDBD module I'll just describe my plans. You can take a look at http://search.cpan.org/search?dist=DBIx-AnyDBD to see the obvious inspiration. Calling $dbh = DBI->connect("dbi:Oracle:foo",,, { DbTypeSubclass => 1 }) will return a $dbh that's blessed into a class with a name that depends on the type of database you've connected to. In this case 'DBI::Oracle::db'. @DBI::Oracle::db::ISA=('DBI::db') is automatically setup for you, if it's empty, so the inheritance works normally. For ODBC and ADO connections the underlying database type is determined and a class hierarchy setup for you. So an ODBC connection to an Informix database, for example, would be blessed into 'DBI::Informix::db' which would automatically be setup as a subclass of 'DBI::ODBC::db' which would be setup as a subclass of 'DBI::db'. The DBI will try to automatically load these classes for you. It'll ignore a failure to load caused by the file not existing. The idea behind this, if it's not dawned on you already, is to enable a simple way to provide alternate implementations of methods that require different SQL dialects for different database types. See below... Finally, putting it all together... These two mechanisms can be used together so $dbh = MyDBI->connect("dbi:Oracle:foo",,, { DbTypeSubclass=>1 }) will return a $dbh blessed into 'MyDBI::Oracle::db'. In fact users of DbTypeSubclass are strongly encouraged to also subclass into a non-'DBI' root class. They are a natural fit together. Imagine, for example, that you have a Sales Order Handling database and a SalesOrderDBI::db class containing high-level methods like automatically triggering re-ordering when stocks get low. Imagine you've implemented this, or just prototyped it, in Access and now want to port it to PostgreSQL... A typical porting process might now be... 1/ Break up any large methods that include both high-level business logic and low-level database interactions. Put the low-level database interactions into new methods called from the (now smaller) original method. 2/ Add { DbTypeSubclass=>1 } to your connect() method call. 3/ Move the low-level database interaction methods from the SalesOrderDBI::db class into the SalesOrderDBI::Access::db class. 4/ Implement and test alternate versions using PostgreSQL in the SalesOrderDBI::Pg::db class. Since PostgreSQL supports stored procedures you could move some of the business logic into stored procedures within the database. Thus your Access specific class may contains select statements but your PostgreSQL specific class may contain stored procedure calls. Random extra thoughts... AUTOLOAD (ala Autoloader, SelfLoader etc) could be put to good and interesting uses in either handling large libraries of queries (load in demand) or even automatically generating methods with logic based on the method name: $dbh->Delete_from_table($table, $key) Oh, scary. The logic for mapping a connection into a hierarchy of classes will be extensible and overridable so that new or special cases can be handled, such as considering/including the database version. Comments welcome. (But please trim replies, and be brief and to the point!) Tim. p.s. Don't forget to CC Matt Sergeant ----- End forwarded message -----