My pet solution: Web framework

milardj on 2004-12-22T02:35:14

Amongst other things I intend to use this journal to discuss the design of existing and new projects and frameworks I have worked on. I tend to design in a vacuum these days (at my old job I was usually hooked up with a partner during design which I found to be educational and led to stronger designs - we both approached the problem from different angles and the design really benefited from that) and I miss having a feedback loop to point out missed opportunities and more effective strategies.

I'm a full time programmer who also operates a consulting/contracting company (very small scale) on the side. About 2 1/2 years ago I was contracted to create a web app which would allow:

  • Uploading of data files (typically market research results) into a database
  • Definition and generation of crosstab reports
  • Definition and generation of regular reports
  • Project, user, and client management

I decided on a framework that would be driven by XML. I borrowed the run mode concept from C::A and defined an XML file that would control the "steps" needed for each run mode:

"steps" could be predefined sql actions (see below) or delegated to a handler as shown above. "handlers" are defined in another xml file and are subroutines that are passed the CGI instance and the database handle: sub { my ($cgi,$dbh) = @_; #-- Do some stuff return $cgi; }

I decided on this approach because I found that a lot of the perl code I needed as handlers were only a couple of lines long and I didn't feel the need to create a package.

In a questionable decision I decided to just pass the CGI instance throughout the framework. Any part of the framework could add new params to the instance or modify existing params. At the very least I should have created a wrapper to CGI. That is one of the items on my To Do list.

As mentioned previously "steps" could also be predefined SQL actions: Where foo_table.xml contains all the SQL related to the foo table: Select col1, col2, col3 from foo where x = ? and y = ? and z= ?

When RM.pm (the module which processes the run modes) sees a SQL step (type = get|delete|insert|update) it calls DAO.pm which is responsible for database interactions. DAO will execute the SQL and determine how to bundle up the result set. In this case because we defined the tag we are telling DAO that we want a more complex data structure returned. The struct tag instructs DAO.pm to return a hash where the key is the concatenation of the columns defined by the key attribute. The structure would be passed in $cgi with the param name "foo_struct" as defined by the cgi_param attribute in the tag.

If our result set is: COL1 COL2 COL3 AAAA 11111 11111 AAAA 22222 12121 Then our return structure is: { "AAAA~11111" => { "COL1" => AAAA "COL2" => 11111 "COL3" => 11111 }, "AAAA~22222" => { "COL1" => AAAA "COL2" => 22222 "COL3" => 12121} } If we specify type="nested" then the return structure is: { "AAAA" => { 11111 => { "COL1" => AAAA "COL2" => 11111 "COL3" => 11111 }, 22222 => { "COL1" => AAAA "COL2" => 22222 "COL3" => 12121 }, } }

By default, if no tag is defined, then each column is added to the $cgi instance where param name is equal to the alias attribute.

Run mode definitions can also import other defined run modes: We can also iterate run modes (or steps) when needed. For example say we have a multi select box and we want to execute a step for each highlighted option:

This run mode will iterate through $cgi->param("delete_user_list") set $cgi->param(-name=>"user_id", -value=>current id) and perform a delete for each user.

We can also iterate at the step level if some steps have to be executed for a list and some only once: rm name="delete_user" next=" userMgr.html">

So in summary - every interaction with the server is defined by a run mode. A run mode can consist of 0 or more steps (if 0 then we are essentially only setting the next page to serve up). Each step can consist of a predefined database interaction or be delegated to a handler.

I've found this to work very well for my purposes and I've been able to add many screens without having to write any new code at all. The SQL xml files are auto-generated by another program I wrote so some new screens (for example search/result/detail screens) can be created by simply defining the templates and by defining the run modes and steps. Since they all follow the same pattern I simply copy the existing run modes, change the template name, change the action names, and I'm done.