Great post, Mark.
I'm fairly new to CGI::Application in particular, but not to perl, nor
the web. My approach to a larger-scale framework has always been from
a multi-use perspective, in that, if I have a large database, it seems
like a shame to write a bunch of code that can only be used through a
browser. (Not strictly true, I know.) I need the same sort of thing
in cron jobs, and other utility scripts.
So what I've always done is build along this idea:
1. Build a robust set of database models. I have a base class that
handles things like loads and saves, builds the appropriate SQL for
me, things like that. Writing SQL is easy, and not something I really
need to do by hand very often, (Structured, right? ;) so I automate
that as much as possible. Table structure lives in those models, and
follows a fairly strict set of rules that I have developed for my own
database design. Logic that is independent of display lives in those
modules too. Relationships, manipulation, etc. Most of the heavy
lifting from a data perspective is done in those modules.
the base class contains routines like:
new, id, buildStatement, loadWithID,
loadWithWhere,loadWithParent,save,delete, procedureCall, and abstracts
for tableName,sequenceName,and orderBy.
The subclasses of that (one per table) use Class::MethodMaker to
declare their structure:
use Class::MethodMaker
grouped_fields => [
IDlist => [ qw( organization_id ) ],
columns => [ qw( organization_name organization_street_address
organization_city_address organization_phone organization_fax
organization_active organization_type_code organization_state
organization_zip) ],
];
sub cTableName {
return 'organization';
}
sub orderBy {
return 'organization_name';
}
2 Then, build some sort of Controller, namely a CGI::App subclass and
then some runmodes and templates to act as views. I try to keep as
much of the really heavy lifting in the data models. Keep the
runmodes simple, and factor where possible. The app/runmodes are more
flexible. I have a setup I'm happy with, but others surely do too.
I use a bunch of helper modules along the way, some homegrown, some
CPAN stuff. A Class::Singleton implementation to pass data and useful
objects around (dbh, config, a logger), a method caching routine for
the data objects to save processing time for expensive routines, a log
facility (Log::Loglite), a config reader, things like that.
I'm working on cleaning this framework up, so that it can be
available, should people want to look at it, or tear it down for
useful parts. The structure of it has glommed together over the past
several years, mostly out of the desire to stop writing a lot of code,
and reuse as much as possible. This is a full-fledged OO approach,
which I understand doesn't appeal to everyone, but it has worked out
nicely for me.
I suppose this falls under methods 1 and 3 in your post, but I'll
leave final judgement on that up to you and others. I'm hoping to
have what I use well documented and cleaned up enough for others to
find useful soon. What I have so far is available here:
http://www.ruejulesverne.com/crApp.tgz
That is somewhere in the neighborhood of pre-alpha. The caching stuff
is not there, and the DB abstraction is not clean at all. It doesn't
handle multi-column keys for tables well, nor does it do joins very
smoothly. Those issues are almost done.
At any rate, thanks again for a thought-provoking post, hope my rambling helps.
caleb
--
caleb rutan
http://www.ruejulesverne.com
On Sat, 2 Oct 2004 02:35:58 +0000 (UTC), Mark Stosberg
<[EMAIL PROTECTED]> wrote:
> On this list there was just a thread which brought to light how
> people access databases through CGI::Application. There were three primary
> groups of answers:
> 1. Custom SQL called directly from the CGI::Application project.
> 2. Using custom "Data Access Objects" to abstract the issue
> 3. Using Class::DBI as a standard solution.
>
> I'd like to discuss this issue a little more broadly: What's an
> effective way to to structure your application once you realize it makes
> sense to move some logic into helper modules? Munging data as it comes
> in and out of the database is probably the most common need for this.
>
> Here are some approaches I can think of:
>
> 1. A purely orthogonal module
>
> Here, the helper module has no dependency or relation to
> CGI::Application. Most other modules you might employ from CPAN would
> fall into this category. For example, using CGI::Simple, SQL::Abstract
> or Data::Grouper.
>
> In many ways, this is the ideal setup, because the lack of
> interdependence reduces the overall complexity of your application.
>
> Unfortunately, writing this of module for a custom solution can tedious
> if there /are/ a lot of interdependencies you want to consider, such as
> sharing configuration data, a database handle, session information or
> CGI query parameters.
>
> 2. A plug-in module.
>
> An example of a plug-in module for CGI::Application is
> CGI::Application::Plugin::ValidateRM.
>
> This technique is technically known as creating a "mix-in" method,
> although I think "plug-in" is a better name.
>
> Plug-ins nave a number of benefits:
>
> - They have direct access to your CGI::Application object,
> possibly including a database handle, configuration variables,
> session information and the query object.
>
> - They are easy to write using standard, simple Exporter techniques.
>
> For these reasons, it's very convenient to write your own 'plug-ins'
> when you want to share some extra CGI::App methods between 2 or
> applications. I use this technique frequently in large applications.
>
> When writing plug-in methods, I suggest following the advice of the
> 'Exporter' docs and don't export the methods by default. This will
> make it easier to trace the origins of where the new methods came
> from.
>
> However, sometimes a plug-in module just isn't a good fit. What you need
> is not more methods for a CGI::App object, but a new kind of object with
> it's own identity.
>
> 3. An OO module with a wrapper interface
>
> [ I'm still evaluating this approach and would like to feedback on the
> idea. ]
>
> The problem here is that you want to write a related custom OO module,
> but you want easy access to a number of parts of your CGI::App object
> such as the database handle, configuration settings, session information
> and the query object.
>
> There are a couple parts to this problem:
> A. It would be a pain to pass all 4 of these elements to an object
> constructor each time, and making them global variables would only
> make things messier. (I've tried that!).
>
> B. You want easy access methods for the session, database handle, and
> config variables, like the CGI::Application plug-ins can provide.
>
> Here's one idea for addressing "A", the constructor issue. Let's say
> we've got a skatepark object. We may want a constructor that looks like
> this:
>
> my $sk8_obj = Skatepark->new( id => 29 );
>
> We pass in the extra information we want automatically by using a
> wrapper method:
>
> my $sk8_obj = $self->init_skatepark( id => 29 );
>
> The wrapper would simply pass additional info into the constructor.
> This keeps the Skatepark object independent of CGI::Application,
> allowing it to be easily re-used by other front-ends. Our wrapper
> method might look like this:
>
> sub init_skatepark {
> my $self = shift;
>
> require Skatepark;
> return Skatepark->new(
> dbh => $self->dbh,
> cfg => $self->cfg,
> session => $self->session,
> query => $self->query,
> @_,
> );
> }
>
> Now, here's a stab at making the data easy to access from the plugin class.
> I simply use Class::MakeMethods to care of creating these simple methods for me:
>
> package Skatepark;
> use Class::MakeMethods::Standard::Hash (
> scalar => 'dbh',
> scalar => 'cfg',
> scalar => 'session',
> scalar => 'query',
> );
>
> sub new {
> my $callee = shift;
> my %args = @_;
> my $self = bless { %args }, (ref $callee || $callee);
>
> $self->dbh( $args{dbh} );
> $self->cfg( $args{cfg} );
> $self->session( $args{session} );
> $self->query( $args{query} );
>
> return $self;
> }
>
> ####
>
> There is a slight "gotcha" hidden in this code. The "cfg" methods does
> not usually work like others. You have to careful of this when using it
> in the plug-in, or add a compatible method to the plug-in module.
>
> I think with these 3 major patterns, most any "helper module" design
> problem can be solved for a CGI::Application based projects.
>
> What Not To Do
> ------------------------
>
> 1. Global variables.
>
> Before I began using these techniques, I tried another. I made these
> things global variables: ($DBH, %CFG, $SES, %FORM). What's the harm I
> thought? I'll treat them as "read only", and thus won't run into many of
> the problems of global variable abuse.
>
> First, some exceptions to the "read only" policy snuck in, despite my
> good intentions. Next, how was I going to get these global variables
> into the helper modules? The options didn't look ideal:
>
> - I could declare the helper module to be the same in the same name space
> as the parent module. This creates dependence and reduces usability.
>
> - I could 'use' the parent module from the helper module. This creates
> a circular dependency, which not only inter-dependent, but it takes
> greater mental effort just to consider debugging it.
>
> It took me years to fully recover from "a few read only globals
> variables won't hurt". Learn from my mistakes.
>
> 2. Re-process the CGI object everywhere.
>
> I was optimizing some code that used this construct a lot:
>
> my %FORM = $self->query->Vars;
>
> Instead of passing around the CGI query parameters I needed, I just
> relied on having the query object handy, and re-created a convenient
> hash of the query parameters. It turns out this is an expensive
> operation in CGI.pm, and it was a primary source of slowness in my code.
>
> As one routine called another, each was re-processing the CGI params,
> so that some lower level CGI.pm functions were being called thousands of
> times for a single request. Oops!
>
> Instead, the code could be made clearer by explicitly passing around the
> form parameters being used, or least a reference to the %FORM hash.
> Finally, just using the OO syntax would avoid this penalty:
>
> $q->param('my_field_name');
>
> I also looked into using CGI::Simple for some frequently accessed cases,
> to speed up the parameter parsing even more. It did provide a noticeable
> improvement, although there are edge cases of (mostly documented) incompatibility to
> consider.
>
> Mark
>
> ---------------------------------------------------------------------
> Web Archive: http://www.mail-archive.com/[EMAIL PROTECTED]/
> http://marc.theaimsgroup.com/?l=cgiapp&r=1&w=2
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]
>
>
---------------------------------------------------------------------
Web Archive: http://www.mail-archive.com/[EMAIL PROTECTED]/
http://marc.theaimsgroup.com/?l=cgiapp&r=1&w=2
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]