The Perl web framework Catalyst has, and I'm fairly sure this is in common with many such frameworks in any programming language, a concept of a store of data, scoped to the current request, called the "stash". I've mentioned a few times that "I don't like it" and the reaction I get suggests that I don't understand it. So it's a good time for me to be ignorant in public so some clever people that do understand can tell me what I'm missing ;-)

Global status

I say that the stash is "request scoped". But really, for the lifetime of the web request, it's a big global pile of random data. Some things would naturally seem to fit there: big things to do with global status, like the logged-in user. So, for example, a base action might check for POSTed login details, and for session cookies, and set $c->stash->{user} to a MyApp::User object. Then, further down the line, another action might want to read user details, and can do so by retrieving it from the stash. This may seem very convenient. But of course, being in a hash, we don't get certain protections. For example, should later actions be able to read the user object? Should they be able to modify it? What about typing? What if someone sets {user} to a String, or an undefined value? It really seems like it might be better to model this kind of global status as an object, for example:
    use MooseX::Declare;

    class MyApp::Status {
        has 'user' => (
            isa       => Maybe[ MyApp::User ],
            predicate => 'is_logged_in',
            is        => 'rw',
            coerce    => 1,
            );

        coerce 'MyApp::User'
            => from 'Str'
            => via { return MyApp::User->from_name($_) };
        coerce 'MyApp::User'
            => from 'Int'
            => via { return MyApp::User->from_id($_) };

        ... # other global status things
    }
This way we get lots of modern/enlightened/whatever Perl OO niceties like coercion, and methods built for free, like is_logged_in. We could also provide a custom accessor that didn't allow the user to be updated if it's already been set, etc.

Passing information down the chain

I mentioned that a base action could set the logged in status near the beginning of the request. In fact, this is a common pattern in many web frameworks, with a whole pipeline of actions happening in order. For example, Catalyst has "chained actions", where a URL like
    /client1/documents/invoices/23
might build a chain like this:
  • login
  • admin login
  • client specific customizations
  • document template setup
  • invoice template setup
  • fetch invoice number 23
It's unlikely these days that you would write:
    method handle client1_documents_invoices ($invoice_number) {
        $self->login          or return;
        $self->check_is_admin or return;
        my %client_args = $self->do_client1_customizations;
        my %template_args = (
            $self->document_template_setup(%client_args),
            $self->invoice_template_setup (%client_args),
            $self->fetch_invoice( $invoice_number ),
            );
        $self->process_template( %template_args );
    }
That would be ridiculous, as you'd have hundreds of such methods... one of the major selling points of a framework is to allow you to combine these little pieces flexibly and elegantly. So you end up with the elements in the chain as separate actions... But how do we manage the flow of data between these items (the %template_args and %client_args in my contrived example above, for example) ? We use the stash of course! So each action can read the values it needs from the stash, and write things later actions will need back into it.
    method an_action_in_a_long_chain () {
        my $foo = $c->stash->{foo};
        my $bar = do_something_to( $foo );
        $c->stash->bar( $bar );
    }
Hurray! Except that you may have noticed that we've reinvented passing formal parameters and return values using global data. Welcome to 1959, enjoy your stay in COBOL! And of course, if we get parameters via the stash, we lose the benefits of typing. And we have to be really certain that the bit of global data we want has been set by the time our action gets called. And, because we have a global namespace, we have to hope that some other action in the meantime hasn't set the value to the wrong sort of data. It also leads to us thinking about our data as singletons. When we realise later that we need to call an action on two separate objects, what do we do? We only have one global slot for the parameter name, so we now have to set it twice (or localize it), and we have to squirrel away the first return value before it gets overwritten (yuck)! Spaghetti and action-at-a-distance may not be the intent of the stash, but they do feel like the logical progression of the concept to me.

Template values

Perhaps the least heinous usage of the stash is as the final store of data to be sent to the template. Each action in the chain will set some information:
    $c->stash = {
        # set by a navigation component
        breadcrumb => [ 'client1', 'documents', 'invoices' ],
        menu_items => [ 'save', 'edit', 'delete' ],

        # set by the login action
        username => 'osfameron',
        usertype => 'admin',

        # set by the invoice action
        invoice_data => $MyApp::Model::Invoice,

        ...
        };
Again though, what's to prevent one action from stomping over the other's data? Will the template die if it gets a hash for invoice_data instead of a MyApp::Model::Invoice object? What happens if some data it was expecting never got set? What's the alternative? I suspect that a "widget" approach might be saner, and I have to confess I still haven't looked at Reaction yet: perhaps that's what I'm looking for?

How do other frameworks deal with this?

I'm not, by the way, bashing Catalyst in particular - it's what I'm using now, but the frameworks I've used or written in the past have also had the features of pipelines of actions, and some kind of flattened global state (whether it was a stash, or just a hash that was passed down the pipeline). Given the problems I'm seeing, I can't believe that this is the final goal, or even the state of the art. So:
  • What am I missing?
  • How does your framework deal with these issues more elegantly? (For that matter, how does Catalyst?) I'm especially interested in learning about how strongly-typed FP stacks (HAppS?) confront this stuff.
  • Where next?