Peter Stuifzand

Discovering structure in web controllers

Alex Stepanov writes in Notes on Programming:

We often get the idea that a mathematical theory is built in a logical way starting from definitions and axioms. This is not the case. The definitions and axioms appear at the very end of the development of a good theory. It invariably starts with simple facts that later on are generalized into theorems, and only at the very end the formal definitions and axioms are developed.

In an effort to become a better programmer (while at the same time rewriting one of my software programs,) we’ll look at a piece of web application code, that I wrote a few years ago. The language used in these examples is Perl.

The idea is to find a better abstraction, to make it easier to add and change code related to the guestbook (and other controllers), while at the same time finding the underlying abstraction that isn’t yet obvious from the code.

The piece of code in question is the list function of the Guestbook controller. It lists a some entries limited by a constant. The function loads the entries from the database using the guestbook_load_entries function.

The webserver and a few other classes parse the url /guestbook/list and dispatch the request to this function. Inside the function we validate all parameters that are needed.

package WebWinkel::Controller::Guestbook;
use strict;
use base qw/WebWinkel::Controller/;
use WebWinkel::DB::Guestbook 'guestbook_load_entries';

sub list {
    my $self = shift;
    my $page = $self->validate(-as_integer => 'page');
    my $entries = guestbook_load_entries($page, 10);
    return $self->render_template('guestbook/list', {
        page => $page, entries => $entries
    });
}

Each of the lines in the function performs a small part of the whole action. I don’t think it’s important to look too much at the syntax. We’re interested in the structure of the action.

The first line in the function validates the page parameter. The guestbook_load_entries function retrieves a list of guestbook entries from the database in the second line. In the third line the controller renders a specific template with the parameters we retrieved from the database.

It seems the structure of the process is quite simple.

  1. Validate the query parameters,
  2. Load some database entities using the validated parameters,
  3. Render a template using the database entities.

There are no branches or loops in this piece of code on this level. The template hides the loops we need for rendering a list.

We can simplify the process to a simple graph.

(1) { Validate → Load → Render }

We omit the specifics in this graph, because we’re looking for an abstraction. Each node in this graph depends on the values provided by the previous step. The Validate step depends on data outside the controller.

Now it’s time to look if this model we just created, can be applied to other controller and their actions. Let’s take a look and see if we can find at least one action that satisfies this model.

package WebWinkel::Controller::Orders;
use strict;
use WebWinkel::DB::Orders qw/orders_find_all_open/;
use Date::Simple qw/today date/;

use base 'WebWinkel::Controller';

sub list {
    my ($self) = @_;
    my ($orders, $payed) = orders_find_all_open();
    my $today = today();
    return $self->render_template('orders/list', {
        today        => $today->format("%d-%m-%Y"),
        orders       => $orders,
        payed_orders => $payed,
    });
}

I found this piece of code in the Orders controller. Let’s see which steps this function contains.

It starts with the retrieval of the open orders from the database on the first line. The second line gets the current date. The third line renders the template with the database gathered in the previous two lines. The structure of the function look like this.

{ Load → GatherData → Render }

This shows us that our previous model doesn’t completely describe all controllers and actions. In this instance the GatherData step doesn’t depend on the Load step. We could switch the two statements and the structure will still be the same. This means we should rewrite this model to include this new piece information. We’ll use the $ to describe a relation between two steps where the first doesn’t depend on the second and vice versa. The model now looks like this.

{ { Load $ GatherData } → Render }

The between the first two steps and Render remains, because all data found in those steps is used in that Step. This model can also be written as follows

{ { GatherData $ Load } → Render }

because we declare the operator $ as being commutative. We can’t yet know if this is an important property of our model, but it describes the examples better. The model is still quite different from the other model.

How could we combine the two models while still being specific about the steps? What have the steps Validate and Load in common? If we look at the two code examples we see that both steps create information that is used in a later step. A later step doesn’t need to be the next step. The step GatherData also satisfies this property.

{ { { Validate → Load } $ GatherData } → Render }

The variables found by Validate are passed into Load, but not specifically into GatherData. How can I say that this model is the same as model (1)?

Let’s say step Validate creates zero or more pieces of information. If a step creates zero pieces of information for the next step, then this is same as not performing the step at all, at least with the current understanding of the model.

This concludes our first look at the structure of controllers and actions. From experience I know that there are more ways to write a controller. We will leave those for another time, just like the specifics about finding out which functions to call. Maybe we can discover a pattern there as well?

© 2023 Peter Stuifzand