[cgiapp] handling data and control flow better (longish)

David N. Blank-Edelman dnb at ccs.neu.edu
Mon Jun 8 01:00:52 EDT 2009


Hi-

So these general "how to think about stuff when you are first  
starting" questions on the list and all of the really helpful, gentle  
responses has finally pushed me to get out of lurk mode to finally  
write my own similar message. A number of months ago I finished my  
first medium-sized CGI::App-based webapp. In the process, I ran into a  
few things that seemed harder than they should be re: CGI::App, but I  
plowed through them. Now that  the project is largely complete, I'd  
like to bring back some of those experiences to this list to find out  
what I should do in the future when faced with similar issues. I can't  
tell if the stuff I ran into was due to my lack of experience or  
approach vs. something in CGI::App, but I'm almost certain it was the  
former. Anything you can suggest that can help me improve how I'm  
working/thinking about these things would be much appreciated.

Here's a brief description of the webapp: needed something to assist  
our users request that their mailboxes be migrated from one system to  
another. I started by writing code that crawled our filesystem trying  
to make intelligent guesses about where the user's stored their mail  
and stored these guesses in a database. The webapp I wrote let user's  
log into it, it retrieved the best guess about their mail storage  
location from the database and also confirmed some personal info with  
the user. If the guess was wrong according to the user, it would take  
in the new info and then attempt to log into our IMAP server to check  
if the user's input was indeed correct (it would show the user the  
folders it found using their mail root path and check if that was  
correct). After the last screen, the webapp actually uses SOAP to  
create the user on the new mail system, writes out the shell script  
necessary to migrate the user and sends mail to our trouble ticket  
system to log the request.  All of this was done with field validation  
(C::A::P::ValidateRM), sessions, and  decent logging  
(C::A::P::LogDispatch). So, not a huge application (~750 lines of code  
w/9 run modes), but still respectable.

While writing this app, I ran into the following pitfalls:

1) At times it was hard to keep all of the pieces of the program's  
state in my head at the same time. For each run mode, I was juggling  
three sets of parameters:
       C::A::P::Session's parameters ($self->session->param())
       CGI's query parameters ($self->query->param(), mostly fed in  
through variables previous run modes definedin forms)
       the data I got out of my pre-loaded database

(didn't use CGI::App's param(), that would have made four)

2) Keeping those things straight was harder because their values  
depended on which run mode I was in at the time and how I got there.  
That was made more complex by C::A::P::ValidateRM, which basically can  
toss you back to the run mode you thought you just left if something  
doesn't pass validation. This meant that every run mode had to  
understand whether it was being entered "fresh" (and hence things like  
the CGI query parameters were more meaningful than the session data)  
or being entered because it was being held back in class due to an  
error (and hence the session data might be more relevant than the CGI  
query data). On top of this, I was also using C::A::P::Forward when I  
needed it. I used it to pass control over to (non-/)recoverable error  
run modes which added another tricky bit of juggling.

So at every moment, I had to be cognizant of:
     1) where I was (run mode)
     2) what data I was handed (CGI query)
     3) what other data did I know about/should I save  
(C::A::P::Session and database fields)
     4) which was the "most current" parameter amongst them
     5) how exactly did I get into a run mode (control flow) or at a  
particular place in a run mode
     6) and where did I need to go next
     7) other stuff needed to program the task at hand

I'm not the sharpest tack in the carpet, but I think I'd argue that  
that's a whole lot of stuff for anyone to keep track of simultaneously  
as they are trying to write a program

In retrospect, I think the added complexity of having to manually cope  
with multiple passes through the same run mode due to validation  
errors was a particularly unpleasant part of the programming process.  
I'm hoping this isn't coming off as whiny or as a rant, but  I  
remember thinking "Why am I having to work so hard? There must be a  
better way."  I believe I had brief thoughts about how something  
transactional would be nice. For example, if validation failed, the  
program would roll back its state so that the run mode could treat  
things largely as if it were entered anew vs. having to have basically  
define two sub-run modes (one for first entry, the other for  
validation errors).

I developed some basic coping tools to deal with these things (happy  
to talk about them), but I'd really like to hear if other people have  
had the same experience as I did. Do you find it hard to keep all of  
the data and control flow stuff straight in your head when developing  
using CGI::App? If so, how do you deal with it?

Thanks for any advice you can offer (and for listening to my questions).

      -- dNb

P.S. I should also mention that mostly, the above issues  
nonewithstanding, it was a blast developing with CGI::Application. I  
really appreciated all of the hard work that went into making it as  
cool as it is and I definitely plan to use it/Titanium in my next  
project.


More information about the cgiapp mailing list