http://qs1969.pair.com?node_id=210883

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

The Problem

On my current project I am redesigning portions of an "administrative console" that allows users to update information that is displayed on the public portion of the Web site. Users log in, select the section they need to work on, select the single action that they want to perform, and then do it. The problem that I am facing is that this is not an "application", but instead is nothing more than a series of scripts that are barely more complicated than a simple form-to-email script. In order to allow the customers to use this product better, I need them to be able to follow a logical "work flow" of several steps.

For example, on this Web site, we have the concept of "products". Adding a product in the admin console is useless in and of itself. The product will not show up on the public side until contacts are assigned to the product. So, for this example, in order to make the admin console more user friendly, it should enable our customers to enter a product and assign a contact or multiple contacts to it. Currently, they add a product and then go back and add contacts. I want this to all be one nice, smooth, work flow.

Further, what if they are adding a product and assigning brand-new contacts? Currently, it works like this:

  1. Add the contact(s) first
  2. Add the products and assign a contact
  3. If multiple contacts are required, go into another screen and select all that apply

While it may not be clear from the above, the user must explicitly choose each section in the work flow rather than have the application walk the user through it (like a wizard). To make matters worse, if they want to add a product with only contacts that don't yet exist, they can't add the product. They have to add the contacts first and then add the product. That's miserable design (I should know, I "designed" it).

A Better User Experience

What I would like to do is create a work flow persistence mechanism that would understand the steps that the application user needs to take and walk them through those steps and be able to add new steps when necessary. For example, let's say that the user wants to add a new product. A simple work flow might be as follows (it's a rotten flowchart because I don't have the right symbols):

The Rotten Flowchart


    +-----------------+
    | Add new product |
    +-----------------+
             |
             V
    +-----------------+
    | Assign contact  |<-.
    +-----------------+  |
             |           |
             V           |
    /-----------------\  |
    | More contacts?  |--. yes
    \-----------------/
             |
             V
          no: stop

A Solution

I am trying to figure out the best way to model something like that. I want to create a work flow object that I can persist in the database that's tied to a user. My initial concept is to predefine work flows and walk through the steps. As each step is completed, move to the next step. That would be easy, but inflexible. In the example above, what happens if the contact to assign to a product doesn't exist? The user is still forced to create the contacts first because simply walking them through predefined steps doesn't allow for the unexpected.

A Better Solution?

If the contact doesn't exist, the workflow needs to take a detour to the "create contact" area and I think a stack based system would be more flexible. In our database, we have the concept of "people" and they are assigned roles, one of which is "contact". If I need to detour from assigning contacts to the "create contact" area, I push the "create contact" action onto the stack (which previously has "assign contact to product" on top). Then, I determine if I need to assign the contact role to a person or create a new person. Those involve pushing different actions onto a stack. Once done, I pop those items off the stack and proceed back to the top stack item. I think this is a more flexible approach, but I have never done anything quite like this before.

The Inevitable Cries for Help :)

Does anyone see any flaws with this plan, or do you have better ideas? Further, are there any Perl modules out there that already handle this for me? (I have no idea what to look for).

Incidentally, we don't use CGI::Application. I don't think it will handle something quite as complicated as this but I could be wrong. Also, we use Template Toolkit which CGI application does not support.

Cheers,
Ovid

Join the Perlmonks Setiathome Group.
New address of my CGI Course.

  • Comment on (OT) Work flow in Web based applications

Replies are listed 'Best First'.
Re: (OT) Work flow in Web based applications
by perrin (Chancellor) on Nov 06, 2002 at 20:33 UTC
    This isn't such a unique problem, it's just a standard multi-form input app. You can certainly model this in CGI::Application or anything else. I would suggest you steal ideas from CGI::MxScreen. I would also suggest you don't tear your hair out trying to come up with a generic solution. If you just code it as a normal multi-page form app with separate actions (or run-modes or whatever) for each page you will probably start to notice redundancies which you can then factor out into supporting classes.
Re: (OT) Work flow in Web based applications
by Aristotle (Chancellor) on Nov 06, 2002 at 21:01 UTC

    I don't think you need to pull out the heavy artillery.

    "Defining a workflow" is a good way to put the solution. What you do is define what data a single task requires, and keep track of the data in a session object or something of the sort.

    In your case, that would be:

    • Product data
    • Contact
      • name
      • data
    • Contacts listed completely

    The last item is simply a flag; it is set when the user says he is done adding contacts. Then you define a series of forms that collect various bits of this data; f.ex, if the user enters a known contact name, you can pull out the corresponding data immediately (or just set a flag that it's there). Then your logic simply looks at the blanks in the session data and branches to the form for the first unfulfilled prerequisite.

    If you completely decouple that last step - deciding where to send the user based on the blanks in the data - from the code that collects the user's response and fills out whichever blanks it can accordingly, the desired behaviour of leading the user through all the steps will just fall out naturally. What's better, as you add or remove bits from the definition of the session, the framework will automagically send the user to the right forms.

    Makeshifts last the longest.

Re: (OT) Work flow in Web based applications
by mojotoad (Monsignor) on Nov 06, 2002 at 23:08 UTC
    Hmmm, I smell the dastardly phrase "Business Logic" in here somewhere...

    As Aristotle said, you probably don't need to pull out the heavy artillery here. When I think of heavy artillery, I think of transactions based on EDI or XML events, millions a day. Folks hurl a lot of money at the problem and names like Peoplesoft, Siebel, PaperFree, Foresight (Edisim), etc, start making appearances and many many specialists storm the facility for implementation or upgrades.

    At their core, however, we're talking about some pretty simple processes such as adding a contact or initiating a billing event, munging data into a different format, etc. The only thing that makes them scary is the amount of money riding on the transaction sets.

    One thing you will find in this sort of business logic is a lot of attention to dealing with events such as cancellations that might occur mid-way through. Rolling back changes can sometimes introduce lots of complexity into a problem which started out as simple.

    I'm not sure how transient your "products" and "contacts" are during the data-entry phase -- it sounds as though there's an intrinsic dependency in that you do not want a product without a contact (but it's probably okay to have a contact with no products?) I'd probably introduce a transaction key that will roll back changes if a cancellation is initiated or if the process is never finished after a certain time period (they close their browser mid-way through and never come back). Depending on the type of session, you could have multiple transaction keys -- each key for a single transaction set and the required events composing that transaction set. One of the "transaction" keys could just be a more familiar session key that helps with your lateral browsing across the form space of your application.

    Matt

Re: (OT) Work flow in Web based applications
by kshay (Beadle) on Nov 06, 2002 at 21:25 UTC
    I can see some problems with using a mode stack in the context of a web application. I've seen this approach used to good effect for desktop applications, but I think it's a little trickier for a CGI script.

    The first thing that comes to mind is, what do you do with incompletely entered data? Let's say your "add a product" screen has 10 fields, plus the "assign contacts" area. I fill in all the fields, then realize my contact doesn't exist yet. So I jump to the "add a contact" screen.

    In a desktop application, this is fine; the contact entry screen appears "on top" and once I'm done adding the contact, I return to the product entry screen, with the state of everything (including whatever I typed in those 10 fields) fully preserved.

    But in a CGI context, what happens? Do you create a new product record in the database, with my (perhaps partially entered) product information? If so, there are obvious potential problems. (What if I realize I don't know all the contact information yet, and it's time to go home, and I'll deal with it tomorrow?) But if not, then when you return me to the product entry screen, I have to re-enter those 10 fields.

    I'm just thinking out loud here... haven't really thought it through. But I wonder if you could use popup windows and/or frames, combined with JavaScript/DHTML for updating, to get the same effect you have in a desktop app where a new mode can be pushed onto the stack without touching the state of anything beneath it.

    Sorry if this was less than lucid. I'll try to elaborate and/or clarify when I have a chance.

    Cheers,
    --Kevin

      But I wonder if you could use popup windows and/or frames, combined with JavaScript/DHTML for updating, to get the same effect you have in a desktop app where a new mode can be pushed onto the stack without touching the state of anything beneath

      I would be leery of that. A great temptation with web apps is overdesign and non-standard behavior, and using frames with DHTML is just asking for trouble. You'll end up having to support ten versions of your code for different browser/OS configuratons.

      One good idea is to spend some time on sites like Expedia, JetBlue, Amazon, Southwest Airlines and other web apps that have to maintain a lot of state and allow for users to do lateral navigation. Steal ideas from them, notice how they do error handling, UI design, and so on.

      When that is done, mock up your HTML pages. Then write code using fictitious subroutines / objects / whatever you like, at as high a level of abstraction as you can. Once you are satisfied with the overall architecture, move down a level of abstraction, using stub objects / subroutines, and then move down another level, until finally you have coded the whole thing out.

      Of course the process is never so pat and linear, but I've found it's a good model to work from. The most important part is looking at lots of other web applications, preferably ones run by companies that have poured money into usability testing.
        I would be leery of that. A great temptation with web apps is overdesign and non-standard behavior, and using frames with DHTML is just asking for trouble.

        You're absolutely right. I wrote that node in haste and now I can repent at leisure. But let me clarify. I certainly didn't mean that Ovid should blithely use every DHTML trick in the book. What I did mean was that if you have a page on which the user is entering data, and you want to update some subsidiary data on a different page, it might pay to put up the entry form for that subsidiary data in a popup window. And once the user has entered that data, how do you communicate that fact to the parent window, and how does the parent window display the newly entered info to the user? JavaScript and DHTML. That's really all I was trying to say.

        You'll end up having to support ten versions of your code for different browser/OS configuratons.

        That's certainly an issue when you're writing a web app for the general public, but when you're working on an intranet-based app that a strictly limited group of users will access, you probably have a certain amount of control over which OS and browser the users will be using. I don't think it's unreasonable to write platform-dependent code in such a case.

        One good idea is to spend some time on sites like Expedia, JetBlue, Amazon, Southwest Airlines and other web apps that have to maintain a lot of state and allow for users to do lateral navigation. Steal ideas from them, notice how they do error handling, UI design, and so on.

        Absolutely. But also pay attention to where they still screw up. Some of the most annoying experiences of my online life have come when trying to price airfares. If Expedia and Travelocity and their kin work smoothly now, it's only after YEARS of sucking, and falling prey to some of the worst pitfalls of CGI-context state maintenance.

      That's what I addressed. You have to define what a complete set of data is and keep track of it on the server. Then, by looking at the blanks left in that set of data, you send the user to the pages he needs to fill out. Finally, you have accumulated what appears to be a complete set of data; then and only then do you actually commit any changes to the actual database.

      It really is quite simple - sort of a reduced model/view/controller design.

      Makeshifts last the longest.

Re: (OT) Work flow in Web based applications
by Theseus (Pilgrim) on Nov 06, 2002 at 23:09 UTC
    Well, I've never done multi-page forms that maintained a state in Perl, but I have done a hell of a lot of Perl web programs with multiple pages, and I definitely understand the trouble you're going through.

    Just an idea, but have you considered adding a link to the Add Contacts page from the Add Product page that pops up in a new window? If you give your users a popup window that they can use to put in contacts using your current Add Contact form, and they can do it WHILE they enter products. I'd consider it the same tactic as popping up a dialog box in a Windows application. Just an idea... There are several tricks you can apply interface-wise with web programs that I've learned over time and they have always helped me out of major coding sessions implementing a feature that could be reasonably duplicated by making the browser meet you half way and jump through a few hoops as well.
Re: (OT) Work flow in Web based applications
by bart (Canon) on Nov 07, 2002 at 01:17 UTC
    Well, excuse me for stating the (almost) obvious, but I'll say it anyway: it appears to me like you want to implement some form of a state machine. Each form is a state, with its associated data. Depending on what the user does, you move to another state.

    I would think that a general implementation of such a state machine, complete with HTML for the forms, could be implemented on top of a database for storage. The tricky part for such an approach, would be in the transitions, e.g. data validation: that must be pure (Perl) code, and I wouldn't go as far as to store that source code in a database. Hardly.

Re: (OT) Work flow in Web based applications
by domm (Chaplain) on Nov 07, 2002 at 08:25 UTC
    In addition to the generic solutions/replies, here is an a little bit more detailed explanation of how I solved a similar problem:

    The project involved Case Studies of some sort, each Study consisting of some data (description, location etc) and an unspecifed number of contact adresses, URLs and file attachments.

    If you insert a new Case Studie, the application has to go the following different steps:

    1. Generate a form to insert the generic date (title, description, ..)
    2. Save this date, thereby generating a new object in the DB
    3. Insert Contact Adresses
    4. Insert Links
    5. Upload Files
    6. done
    The URIs to handle these steps looked like this:
    1. /casestudy/insert/1
      generate form, action of the form-tag is '2'
    2. /casestudy/insert/2
      generate new object in DB - now we knoe the ID of the object and redirect to
    3. /casestudy/case0001.html/insert/3
      Two different things happen here:
      • List all linked contacts (including buttons to unlink them etc)
      • Add new contatcs:
        Call /contact/insert/1?link_with=case0001 and thus start inserting a new contact (or selecting an existing one), link it with the Case Studie and the redirect to /casestudy/case0001.html/insert/3 to repeat the process
      Additionally, there is a button called "finished with contatcs" that takes you to:
    4. /casestudy/case0001.html/insert/4
      same as above, only with links
    5. /casestudy/case0001.html/insert/5
      now fileupload
    6. done.
    The main point here was that I defined insert sequences for each data type that would work on there own. On the one hand this was a requirement (i.e. you should be able to just add links without a Case Study), on the other hand, this saved me from going crazy because I could implement and test each data type on its own and then just link everything together.

    All this is done with some mod_perl handlers, but you could just do it with CGI and "regular" parameter passing (i.e.: /cgi-bin/insert.pl?type=casestudy&step=3&id=case0001 )

    You can take a look at a (German) implementation here (with News, not Case Studies, but it is basically the same..), but please don't enter too much junk, because somebody will have to weed it out afterwards...

    -- #!/usr/bin/perl for(ref bless{},just'another'perl'hacker){s-:+-$"-g&&print$_.$/}
Re: (OT) Work flow in Web based applications
by Ryszard (Priest) on Nov 07, 2002 at 09:39 UTC
    This will need a little bit of working thru' but how about something like this as a starting point:
    sub workflow { get workflow step from button or link selected. if (not step1) { %existing_detail = get stored details identified by unique session id of user %existing_detail .= new details $self->store(\%existing_detail) } my $page = $self->draw_page(page => workflowstep, params =>\%existing_detail); return $page } sub draw_page { my $wrkflwObj = Workflow->new(step=>$step, param=>\%existing_details +); return $wrkflwObj->output; #return the html }

    So, the logic of your workflow's belong in your templates. You get to aggreate all the information the user supplies, and know which step the user wants to go to next. If the user wants to add a product, and the contact doesnt exist, you can take them to a step in the contact workflow, then return them to back to where they need to go.

    Flexible, as you can extend the workflow in arbitrary ways.

    draw_page could potentially call a library with the method being the step in the workflow, the library would then know exactly what information is needed then return back the html page.

    Not really a down side, but you'd have to be careful how you manage the expiry of each set of \%existing_detail, ie if someone doesnt click the cancel button and walks away, then comes back a day later - should their day-old data be restored?

    Potential downside, you'll have to be carefull how you manage multiple incomplete "actions" per user, ie if they are 1/2 way thru' the add product action and they want to go to the modify product workflow, what do you do?