[cgiapp] Model design in C::A/Titanium

Ron Savage ron at savage.net.au
Tue Oct 21 19:45:41 EDT 2008


Hi Mark

On Tue, 2008-10-21 at 15:20 -0400, Mark Rajcok wrote:
> >
> > I would appreciate some feedback on a possible MVC structure for a
> > Titanium/CGI::App that I am currently building. I'm using CA::Dispatch to

I too use CGI::Application::Dispatch - a fine module indeed.

> > allow multiple apps with a small number of rm's each. Views are handled by
> > CAP::TT. Just the Model aspect is proving a little challenging. I'm trying
> > to ensure that all database calls are handled outside the controllers, and
> > to this end am using a module called WebApp::Model
> >
> > This seems to work well, but the problem is that WebApp::Model is growing,
> > as ever more (unrelated) db-related methods are added. Perhaps I

But why /exactly/ is it growing?

I've just had a look at my Local::Contacts::App::Database.pm, and it
contains methods with these sorts of names:

o get*. 27 methods (gasp!)

That's what you get for using DBIx::Simple rather than a full-blown ORM
such as Rose.

And for eschewing AUTOLOAD.

o log*. 2 methods

One logs a string to the log table, and one logs a transactions (per
table + field) to the edits table.

Yeah, yeah, I could use Master-Slave replication for the latter, but I
wanted to study the process to better understand my database and code
design.

o new. 1 constructor

o save*. 5 methods

I should say the module is unfinished.

Only people, email addresses and phone numbers are saved so far.

It's a web-based contacts manager (i.e. address book) which will have
other modules glued on, e.g. donation management.

o validate*. 9 subs

Very few of these are called from  the app, those few being to validate
a user at log on and to save data.

All others (validate*) are called by Validate.pm (which uses
Data::FormValidator).

I'm comfortable with all those being in Database.pm. Are you :-))?

What else are you putting in there?

Now, here's my add person run mode (called via Ajax) in Person.pm:

sub add
{
  my($self)   = @_;
  my($result) = Local::Contacts::App::Validate -> new(app => $self) ->
add_person();

  # Now $result is a Data::FormValidator::Result object.

  return $self -> add_report($result);

} # End of add.

Neat, huh?

>From the app parameter it can get the database object.

Indeed, that add_person() is just:

sub add_person
{
  my($self) = @_;

  return Data::FormValidator -> check($self -> app() -> query(), $self
-> add_person_profile() );

} # End of add_person.

And how do I combine this with the app?

sub cgiapp_init
{
  my($self) = @_;

  # Warning: Must create database object before calling log().

  $self -> param(config => Local::Contacts::Config -> new() );

  $self -> param(simple => DBIx::Simple -> connect(@{$self ->
param('config') -> get_dsn()}) );

  $self -> param(database => Local::Contacts::App::Database ->
new(config => $self -> param('config'), simple => $self ->
param('simple') ) );

...
}

Via that last call, the db object 'has a' DBIx::Simple object, and to
use the model the app always has to do $self -> param('database') ->
something(...).

Now you can see how it's kicked off via cgiapp_init(), and then the
add() run mode does some work.

So, the database code can change without affecting other code, as long
as the /format/ of the returned data is the same.

And the validation code can change likewise. If I need more validation,
I add code to Validate.pm and Database.pm.

There are only 2 special cases (so far) in returning error msgs, both to
do with handling uniqueness of data: personal names and usernames.

> > could/should sub-class this one? Ideally I would move the WebApp::Model
> > methods into their respective WebApp::DB class (eg get_foo() goes into
> > WebApp::DB::Foo), but then I can't access it any longer from
> > $c->param('model')->get_foo.
> 
> 
> I struggled with the "model" part of MVC recently also.  Pages like
> http://cgi-app.org/index.cgi?OrganizeApp don't really address the model
> part, other than to essentially say "best practice: keep DB stuff outside
> the controllers".  (I plan to update that page in the near future, to
> discuss how to model the model part.)

You're right here. I've often found the discussions of the exact nature
of the components to be a bit vague.

Of course, tying things together can be clear or murky. I feel my code
is very nice in this area, but I have never written an article spelling
out what I do, so perhaps putting it into words is the stumbling block.

> After reading through some of the archives on this mailing list, and this
> thread, the consensus seems to be to create a database-aware base class that
> model objects can inherit from.  Often, each "model object" models a
> database table.  Normally an ORM module (Rose, Class::DBI New Code:
> DBIx::Class) is used.  Creating your own WebApp::Model class is probably not
> "best practice".

Hmmm, not sure about this. I've never needed a 'database-aware base
class that model objects can inherit from'.

Are you sure you're not adding too much complexity here because someone
somewhere once did it that way?

No shame in that of course. When we just don't know how to do something,
it's quite reasonable to follow someone else's guideline.

But the latter necessarily involves learning and thinking about what's
really happening, and you need the nerve to discard anything you don't
like the look of.

Can you give me an idea is to what would go into the base class of such
a set up?

> I decided to create my own base class, MyDB::Object.  This object is
> intentionally similar to Rose::DB::Object -- it has load, save, insert,
> update, and delete methods -- so that when I eventually migrate to RDBO, it
> will be smoother (don't ask why I am doing it like this).  This base class
> uses AUTOLOAD to create accessor methods for each field (using code from
> Conway's book Object Oriented Perl).  So for example, if my User table has a
> 'u_password' field, I can access it like so:

I never use AUTOLOAD myself.

I guess that explains the 27 methods called get* above...

> my $user = User->new(u_username => 'mraj');
> $user->load;  # populates object from the database
> print $user->u_password;  # get pw, AUTOLOAD creates u_password method
> $user->u_password('new_password');  # change pw
> 
> The User.pm class (that models the user table in the database) is rather
> simple.  It in herits from MyDB::Object to get common DB operations.  It has
> an array of field names, and a method that overrides the base class to
> return the field names as an array ref.
> 
> I have a controller (ManageUser.pm) that inherits from CA, and it only has
> runmodes that handle user account related operations (as per "Quote 3" on
> http://cgi-app.org/index.cgi?OrganizeApp).  Here's a sample runmode:
> 
> sub display_user_form : Runmode {  # this runmode can only be called if
> authenticated
>   my $self = shift;
>   my $user = User->new(u_username => $self->authen->username);
>   $user->load;  # calls the base class method in MyDB::Object
>   my $t = ... load your HTML template ...
>   foreach my $field (@$user->fields) {  # fields is a method I wrote in
> class User.pm
>     $t->param($field => $user->$field);
>   }
>   $t->output;
> }
> 
> There we go... no DB access in the controller... only the model (class User
> and MyDB::Object) access the DB.

OK. I guess we agree on this one.

> There's one wrinkle I left out... how did object $user get the $dbh that the
> controller created?  Well, based on another discussion from the archives, I
> created a singleton class to store the $dbh.  In cgiapp_init, I create the
> singleton and pass it the $dbh:
>   MyEnv->instance(dbh => $dbh);
> Then the model classes only need to use MyEnv (they don't need to use any CA
> clases):
>   use MyEnv;
>   ...
>   my $r = MyEnv->instance->dbh->selectall_hashref(...)
> 
> http://perldesignpatterns.com/?SingletonPattern
> 
> Class diagram:
> 
> C:A        MyDB::Object    MyEnv   user.cgi
>   |            |
> MyBase        User
>   |
> UserManager
> 
> UserManager.pm inherits from MyBase, which inherits from CA.
> UserManager 'use's model objects like User.pm.
> MyDB::Object uses MyEnv and DBI.
> MyBase creates the MyEnv singleton (and handles
> login/logout/authentication).
> 
> Runmodes related to other operations (say inventory items), would have an
> inventory.cgi instance script, an InventoryManager.pm class that inherits
> from MyBase for the runmodes related to inventory, and one or more model

Yes, for me.

> classes (e.g., Item.pm) that inherit from MyDB::Object.

No, for me.

> But the main question is whether this is the right approach to start with -
> > stuffing WebApp::Model into the WebApp object and retrieving it in the
> > run-modes via a param_name. Advantages include decoupling the model from the
> > controllers - they don't know/care how get_foo() gets its data, and I can
> > change the db-related stuff (eg RDBO <=> DBIC) without affecting the
> > controllers. Disadvantage (so far) is lumping all methods into one
> > (WebApp::Model) module. Comments & thoughts most welcome.

I do this myself, so I'm forced to concede your design is brilliant :-).

> > Have a base class of WebApp::DB;
> > Have WebApp::DB::Foo inherit from base class;
> > Make the base class smart enough to auto-load WebApp::DB::Foo and
> > instantiate it when needed.
> > A little autoloader magic goes a long way, and you get to get rid of all
> > the
> > "get_foo" stuff, and just do foo->get().
> 
> 
> Personally, I don't like objects popping into existence.  I prefer to "new"
> a Foo object when needed.

Agreed.

> >From an OO perspective, it also seems odd to me that a base class could
> instantiate a subclass (base classes shouldn't know about their subclasses).
> Also, what if you need to create more than one Foo object in a runmode?  The
> "auto instantiate" mechanism won't work.
> With the code I show above, a runmode can create multiple user objects if
> needed:
> my $user1 = User->new(...);
> my $user2 = User->new(...);

This is a very interesting comment.

Thinking about it, I see my code would /never/ create a Person object (I
have no such thing, funnily enough).

My Person.pm is my person-controller object. It takes decisions because
it contains all person-related run modes.

If it wants person data, it calls Database.pm's some_method() to get a
data structure, which then gets formatted into either HTML or JSON.

I suppose I could have designed a person object, and gotten the
controller to create an instance, then gotten the database to populate
whatever bits a specific run mode was interested in, and then gotten the
person object to format those bits for output.

That would mean this person object was a person view component in a MVC
structure.

I've just never bothered adding that sort of complexity. The formatting
- such as it is - takes place right in the controller, my Person.pm.

-- 
Ron Savage
ron at savage.net.au
http://savage.net.au/index.html




More information about the cgiapp mailing list