To a large degree web application state in most apps amounts to C++ memory management when done right, and C++ memory management when done wrong (in other words, a pain in the butt when done right, and painfully and quietly broken when done wrong). It's not hard to do better, I think.
So, two steps. First, move state out of flat maps. NanoWar nailed the best non-continuation based system I have yet seen for this (continuations later). It isn't quite perfect yet, but it is close. Providing structured scopes in the three classic contexts is a start, though.
Once you have that you can add additional scopes. There are, in fact, quite a few typical scopes in web applications which are only rudimentarily addressed:
This certainly isn't exhaustive, but the scopes exist, and we use them now, but we do it by hacking them into others. By exposing them, we can make things much more clear.
Presently the "real" scopes are request and application scopes, session is a (not quite) kludge managed by mapping some identifying token to a map stored in the application scope. I call it a kludge, but it is actually quite elegent -- if all you have is a flat space (global scope). Franky, unless you go to a better model, it is probably the only real option. Possibly overlaying a stack-based scope (same name occludes broader scope, like Lisp had originally (from what I have heard)) could be done, but may not be a real benefit.
So do the same thing! JSESSIONID
, JWIZARDID
, JTRANSACTIONID
, JLIFETIMEID
, etc. You'll probably need to provide a convenient accessor, but that isn't terribly difficult. Fetch the specified scopes from the application scope by id, (ideally using cryptographically secure tokens), and plunk them into some kind of container in the request. Voila.
Now we have arbitrary scopes, but we don't have an easier way to manage them, and we have potentially left ourselves with a big fat memory leak. Aside from reasonable timeouts, encapsulate the scope in something which supports a lifecycle. Sessions, transactions, wizards, etc all have quite logical and apparent begin/end demarcation points. As we already have them in a container, lets clean it up some and use a container-per-scope (a la Nano) exposing a container lifecycle controller as a component in the container.
The action which concludes the wizard is dependent upon the wizard controller and can call the Context.close()
to clean up after itself. Anything bound to the context is flushed nicely. People *can* walk away from a context, but, and this is nice, if they hit the back button to get back to it, the id's are in the url (we are using url's here, not cookies, as you can have n wizards and user transactions going, etc, though I can imagine cookie schemes which will work. You explicitely rejoin a scope and it switches the id cookie out, etc -- lots of byzantine possibilities). A better scope tracking system is probably worthwhile, as scopes of similar types can certainly overlap. Ah, food for thought...
Now, we have arbitrary (with some typically convenient ones spec'd) scopes, and one viable alternative for picking them (and others imagined). All this work and... it is simply to emulate behavior had for free in tools using (RIFE) the better (Cocoon) mousetrap (Struts-Flow) already mentioned ;-)