http://www.perlmonks.org?node_id=200555

Nomis52 has asked for the wisdom of the Perl Monks concerning the following question:

I keep hearing about how when designing database based apps, the database access should be kept in the low layers (or in this case objects)

Take a person object for example,
$person = Person->new('John',23) ; # later on $person->getAge ;

This would INSERT the entry in the db when new() is called, and do a SELECT when gettAge() is called.

The relation in the db may look something like this:

pIDNameAge
1John23

Now the thing that I can't get my head around yet, is how say getAdults() should work. So far I have come up with 2 possble ways to do this:

The first way I thought of was to have getAdults() as a class method which would run the SELECT .. WHERE on the db and return an array of people. By doing it like this it keeps all the code accessing one table in one class. Probably a Good Thing.

The second would be to create a separate class say PersonLister which contained the method getAdults(). I think this way is more OO in the sense that the Person class knows only about people, and higher classes know how to fetch groups of people.

What are the monks thoughts on this? Is either of these the prefered way to do this or am I completly missing something?

Fixed typo per author's req. - dvergin 2002-09-24

Replies are listed 'Best First'.
(jeffa) Re: OO Application Design
by jeffa (Bishop) on Sep 25, 2002 at 04:41 UTC
    getAdults() sounds like a specific version of, say, a search() method:
    # warning, not what you really need my @adult = Person->search(age => 18);
    which is more than likely a class method. Say, you aren't rolling your own are you? Check out Class::DBI and it's oh so useful extension, which allows you to do things like:
    @adult = Person->retrieve_from_sql('WHERE age >= ?', 18);
    which is what you really most likely want (you could also wrap that code in the method getAdults()).

    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)
    
      I did something fairly similar recently - to paraphrase my POD (Yay! I'm so glad I wrote it now :-) ):
      Person->get(name => "Foo Bar"); Person->get(name => {method => "EQUALS", value=>"Foo Bar"}); Person->get(name => {method => "REGEX", value=>"Foo? Bar"}); Person->get(name => {method => "CONTAINS", value=>"Foo"}); Person->get(age => {method => "GREATERTHAN", value=>18}); Person->get(age => {method => "LESSTHAN", value=>21});

      Where the arguments to the get function acted as constraints, i.e. reducing the number of results you'd get back. I'm not certain that it was the way to do it, but it allowed me to forget that I'm not very good at SQL, I'm terrible at schema design, and even the fact the data is stored in an RDBMS.

      This approach also had the advantage of having one place to put the SQL, and prevented users from throwing potentially bad SQL at the database, or asking for people that had a name "GREATERTHAN" 24.

      Cheers.
      davis
      Is this going out live?
      No, Homer, very few cartoons are broadcast live - it's a terrible strain on the animator's wrist
Re: OO Application Design
by spartacus9 (Beadle) on Sep 25, 2002 at 04:24 UTC
    I have always gone with the class method version, i.e., $person->getAdults(); This way seems to require less code upfront. Typically I put all of the class methods that are generic (including the actual DBI connection handle) into a single Package file and use that file from the perl program. That way once you get all your class methods written, you don't have to touch them again while your're messing with the perl program that uses them.

    That said, if you have a huge amount of data returned by the calls to your class methods, it may be better to create a separate class that only knows about, in this case, people. That could be used to reduce the amount of data you fetch when you only need a certain small portion of the data.
      I used to use this approach, however now have abstracted my dbi stuff into a seperate object.

      One place to make maintaince changes now - not 20 like before..

Re: OO Application Design
by diotalevi (Canon) on Sep 25, 2002 at 04:34 UTC

    I haven't had the opportunity to work with it yet but I hear Alzabo and Class::DBI go some distance to help you solve some of these problems and keep your hands away from having to write much of the lower-level code that nice OO-persistence can require. My thinking is that Alzabo might do even more for you than other solutions since it will also serve as your schema documentation *and* will generate it as well. Then again - I'm perfectly comfortable doing up a very nice schema in PostgreSQL and don't need Alzabo to create it for me.

    Consider those as suggestions. I've got an example of code that does this sort of thing up at jbj-0731.tgz. Browse the Voter::Db classes.

      My favorite O/R mapping module is SPOPS by our own lachoy. Have a look.
Re: OO Application Design
by kabel (Chaplain) on Sep 25, 2002 at 08:26 UTC
    i think the problem here is that perls array type is so high level that you should really consider if you need an extra "List" container class or just return an array of "person" objects. in a "List" class you could add some extra functionality (whatever that may be), but you got to code it (extra code to maintain) - (i got the feeling that this sounds a bit like how you would do it in java?)

    i have experienced that the places where you got to deal with one "object" and with a whole bunch of them is very clearly defined. there is IMO no reason to further generalize the code any more if you are at that stage.

    if you have the time you may give the container class approach a try and make the decision for yourself which of the two suits you and the whole project best. just think on a popular perl saying :)
Re: OO Application Design
by abell (Chaplain) on Sep 25, 2002 at 07:56 UTC

    I believe you should have getAdults as a class method in class Person and let it return an object of type PersonList. A PersonList should just contain the handle of the executed statement (in other words a cursor or ResultSet) and wouldn't hold all elements in memory. It could have methods like get_next, has_more_elements, as_array, letting you either access elements sequentially or get all the bunch as an array.

    In a more general setting, a PersonList could be an instance of a superclass DBObjectList, knowing aware of what kind of objects it is retrieving (in your case Person's).


    Antonio

      I've found this the best way to do it, mostly because it allows lots of generalising and lots of use of polymorphism.

      I have objects of class Person, Invoice, Business etc and they all basically want the same methods called on them. Using a class method Person->newIterator actually returns an instance of PersonIterator which is subclassed from DBIterator.

      PersonInterator, etc, instances are created on the fly and how they behave depends on the arguments passed by newIterator. Because all the Person, Business, etc classes are all subclassed from the same Entity class, the calling program doesn't actually have to know what class it is dealing with.

      So calling: $any_Iterator = $any_class->newInterator

      returns a an instance of something subclassed from DBIterator. Note DBIterator, itself, is abstract.

      Then you can keep calling: $any_Iterator->nextElement;

      until it returns undef.

      Is this a naughty use of symbolic refs? I don't know. But what I do know is that I find this code easy to maintain easy to extend and easy to tweak.

Re: OO Application Design
by strider corinth (Friar) on Sep 25, 2002 at 21:31 UTC
    The best way to design, IMHO, is to build your program to follow the pattern set by your data; for example, if your data is a tab-delimited text file in an OO world, an object representing the file would (a lot of the time) be the appropriate translation. If there were to be more than one file, an array of file objects would probably be good (but see below).

    Here, it sounds to me like the best thing would be a People object containing references to a bunch of Persons. This model maps well to the data as it touches your program (the database is a single entity) and to the way the human mind works: the database contains information on a bunch of people, not just one, so it's best represented by People, not a Person.

    If having Person objects in this interface seems like overkill for your program (though given your description, it doesn't sound to me like it would, unless you're working with a large table) the People object could implement that functionality with subs, like a new_person() function, and maybe find_person().

    By the way, I don't advocate always patterning your code after the way your data is presented to it. The fact that you're reading your data from twenty log files doesn't mean you need 20 file objects. If your program is simply parsing the data from the files line by line and file by file, a Logparser object that reads the logs and returns lines on demand would probably be better. If your program is only going to see a slew of lines anyway, it might as well be presented with them in that form.

    --

    Love justice; desire mercy.
Re: OO Application Design
by jackdied (Monk) on Sep 26, 2002 at 06:16 UTC
    Ideally, it depends on how OO you want to be.
    A good genalized version would be something like:

    Entity (approximately a row in a database)
    EntityHTML (HTML display class for Entities)

    Person ISA Entity
    PersonHTML ISA Person, ISA EntityHTML

    In javaspeak, PersonHTML ISA Person, Implements EntityHTML.

    Since your talking about RL, Person->new() should take a database ID, or an $Entity::NEWOB (a constant), or be created in Person::new_by_name('bob').

    You'll want some way to flush the object back to the database, probably save(). But since (again in RL) you are talking about stuff in a database skip the save() and instead use add() and update() (or your synonyms). Trust me, it saves alot of confusion when reading code versus making save() 'smart'. Low-level things should be dumb, high level things should be smart. Don't forget delete()

    A PersonHTML object just knows how to display a person - and nothing about the database - as an HTML form, or as a row on a list. Depending on your data you can throw in EntityPDF or EntityPNG as well. EntityPerl might be a neat one, but you probably just mean serialize().

    There is lots of other stuff to think about, Factory classes and such, but tackle those when you need them :)

    -jackdied

Re: OO Application Design
by samurai (Monk) on Sep 26, 2002 at 16:11 UTC
    I usually create a library class for this kind of work. I used to put the code in the object of the kind of data I was pulling, but I figured a library would be more maintainable. For example:

    my @users = Project::getUsers();

    So that function returns an array of User objects. If you allow your constructor of your User object to take array references as an arg, you can populate the object on the fly with the result row, and then return the list of objects.

    --
    perl: code of the samurai

Re: OO Application Design
by trs80 (Priest) on Sep 26, 2002 at 01:14 UTC
    What do you want to do with the people in the list? That is how are you going to interact with the ones matching the "adult" criteria? This will/could effect your designed interface to some extent.
Re: OO Application Design
by princepawn (Parson) on Sep 27, 2002 at 01:01 UTC
    Though the functionality is now within DBI, I still like DBIx::AnyDBD for creating database driven apps. You can see an example of code using it in the PApp::Hinduism distribution.