Three-Tiered Design

Learn how to implement middleware into the mod_perl/Apache design mix.

Now that we have a class that handles the people in our database, we need to create an appointments class. For now, we will only concern ourselves with inserting new appointments and retrieving today's appointments.

The design of (see Listing 4) is generally similar to, particularly in the way that it opens a database connection in the constructor and closes it in the automatically invoked DESTROY method. Beyond this, however, appointments keeps no state whatsoever. It merely acts as a conduit to the database, allowing us to create new appointments and find out with whom we are meeting today.

Listing 4. Appointments.p

For example, Listing 5 contains a short program that uses to create a new appointment. We must create an instance of people and another of appointments. Once we have these two objects, we can set the “current person” to be my niece (“Hadar Re'em”), dying with an error if set_current_person_by_name returns undef (indicating failure).

Listing 5.

Once we have successfully set the current person, we can create an appointment with that person. The format of the date and time are dictated by PostgreSQL, which accepts a variety of formats.

We can similarly retrieve today's appointments using the program in Listing 6 ( This program uses the get_today method, which returns a list of hash references. Note that the implementation of get_today uses DBI's fetchrow_hashref method, which is known to be significantly slower than fetchrow_arrayref. However, it makes life much more convenient, allowing us to do print-appointments as seen in Listing 6.

Listing 6.

Finally, we can list all of today's appointments with a particular person with the get_today_with_person method. Of course, this means that we must create an instance of people and choose a current person using one of the methods described earlier. The implementation of get_today_with_person expects to receive an instance of people as its first user-passed parameter, allowing us to use the current person in our SQL query. The program in Listing 7 demonstrates how I can find all of today's appointments that I have with my nephew Shai.

Listing 7.

Designing the Objects

One of the major points of using objects in a middleware layer is the fact that they provide a layer of abstraction. So long as the interface is well defined and remains stable, the implementation can change.

However, as with all programming techniques, designing good objects can be difficult. Perl provides totally open access to an object's internals, meaning that without a good API, programmers using the object might be tempted to reach inside and work directly with the implementation. This might mean that software will break when the implementation changes—the very situation that using objects was supposed to prevent!

In addition, we want our objects' implementations to be relatively separate from each other. During my design of the people and appointments objects, I was sorely tempted to allow Appointments to get and use the numeric ID of the current person. But of course, doing so would violate the abstraction barrier that I created with my object. The solution, which is admittedly not as elegant as I would like, was to create the get_current_person method. This allows appointments to retrieve the current user, without having to know where it comes from. In the end, of course, the return value from get_current_person is placed in an SQL statement and is compared with People.person_id, breaking the abstraction somewhat.

Finally, notice how each of the objects here contains basic logic, but does not store any state. It would be relatively simple, for example, for our people object to retrieve all of the rows from the people table, and to make them available to invoking objects from within Perl. Indeed, such a solution would significantly reduce the overhead of going to a database, and would allow us to perform manipulations in Perl, rather than turning to SQL each time.

But this solution causes many more problems than it solves. For example, what happens if we create two instances of people? Now we have two objects, each of which contains the full set of rows from the people table. If one object modifies its state, that modification will never be reflected in the second object. Worse yet, what happens if both objects modify their state before storing those changes in the database? Perhaps the database is designed to resolve such locking issues, but our Perl objects are not. Furthermore, what happens when we have 100,000 people in our people table? Reading that much data into a database client is a waste of memory, and of the high-performance data selection and manipulation routines that a database server includes.

Our objects are thus pipelines to the database, giving our web application the ability to talk to a database without having to include any SQL or knowledge of the tables' layout. The objects, by providing a standard API, make it possible to change the underlying implementation without having to announce those changes to the world.