In the spirit of
stages of grief, these are the
stages of adoption of new software development techniques:
UPDATE: Hopefully this does not look too much like a bite of Gartner's Hype Cycle.
- Stage 1, Rapture: The
shiny new toyphase, where we first discover a new idea, and realize how powerful it is. As we get confident, we start trying to use it for /everything/.
- Stage 2, Disillusion: Inevitably, as we start trying to apply this new approach to more and more complex and obscure problems, we start to run into cases where it doesn't quite fit - either because the expression of the idea (the current implementation) is still immature and imperfect, or because in our excitement, we're simply misapplying it.
- Stage 3, Insight: The shiny toy is now just another gadget in our toolbox. We finally have the experience to know when and when not to use it. We understand not only the limitations of the idea, but also /why/ the limitations exist.
The earliest release of Seam was a whole year ago, and Seam is now I guess the fastest growing framework in Java (500 downloads each day and rising). But the ideas in Seam - contextual components, state management, conversations, BPM - are still new to most people and the Seam community is still in the process of understanding exactly how and when to apply these ideas. This is actually the most exciting phase for a project like Seam. I loved the early days of Hibernate, when the community was still developing the patterns (and finding the language for describing those patterns) that are now formalized into books and examples and known to most enterprise Java developers.
Today I got an email from a user who had just hit Stage 2, Disillusion. He asked:
- If is best to use page flows or is normal JSF navigation in faces-config.xml enough?
- Is there a one sentence explanation on how to use @Begin and @End? I think most of my problems are due to misunderstood and invalid usage of conversations.
I spent more than a year thinking about conversations and optimistic transactions and designing Seam's conversation model before I actually sat down and wrote my first two Seam example applications a year ago. Even so, I got it wrong in both cases! I actually managed to screw up the conversation model on the first release of our beloved Booking demo. My lame-ass excuse is I was trying to illustrate the /idea/ of conversations, while keeping the actual conversation model extremely simple. But of course the worst way to illustrate the power of an idea is to misapply it. Mea culpa.
A year later, I think we're finally at the point where we can start to formalize a set of rules of thumb on where to use conversations, where not to use them, where to begin them, where to end them. We now have the hard-won practical experience to go with the idea and its implementation.
It's very difficult to define a correct state management model without interaction with the users. The first thing we need to understand is how the users want the application to work: what tasks should be multi-taskable, what pages need to be bookmarkable or exchanged in email, when should work become persistent, etc.
My first stab at
when should I start a conversation? is the following:
Start a new conversation if you are at page X, and:
- the user is expected to want to navigate to page Y using
open in new tabor
open in new window, and
- you want some state that is retrieved while rendering Y to remain in memory on the server, but isolated to the tab or window.
We need to supplement this with the following additional rule:
- If page X already has a conversation, and page Y needs access to the state held in X's conversation, then make it a nested conversation. Otherwise make it a whole new toplevel conversation.
Well, those rules are really just a restatement of the definition of
nested conversation. They don't really tell you anything you didn't already know (though it is perhaps a more friendly formulation). What we really need is a characterization of the kinds of things that X and Y can be. So, when I'm developing frameworks, I really need to think about the following canonical cases:
- Search/list screens: these let the user find and navigate to the entity instances they are interested in. Search screens often support sorting.
- Item detail pages: these let the user view not just an instance, but also associated objects. Sometimes these pages can be complex views driven by a treeview control and requiring lazy fetching of data possibly via AJAX.
- Edit/association management pages: these pages implement simple optimistic transactions that let the user atomically view and update an entity instance, and possibly change its association to other entities. I'll throw create and delete functionality into this bucket for now, though these should strictly have their own categories.
- Wizards: I'm not just thinking of simple linear flows here, but rather of any interactions where we guide the user through a series of steps that form a single unit of work. In fact, a
wizardcan even be something as complex as a treeview control where the user can edit data from a toplevel object and its child objects spanned over several different pages, lazily fetching data and caching edits in memory on the server, and then finally make all the changes persistent in one atomic
The answer to my Disillusioned user's first question is simple: /only use jBPM pageflows for wizards/. The stateless navigation model is much more appropriate for our other canonical cases.
But I still havn't satisfied his request for a
one sentence explanation of when to use a conversation. Instead, I'll try to sketch out a set of steps to take when designing a disciplined state management model for an application:
The first thing to consider is the search or list screens. These are extremely important, since they are the entry point to all application functionality. We need to decide where to
put the search result list. Alternatives include:
- keep their result list in a conversation - this is usually overkill
- keep the results in the session context - not good if the user needs to be able to run multiple simultaneous searches
- go stateless (use a redirect with request parameters and pull MVC) - the advantage is the results can be bookmarked; the disadvantage is that we canít easily cache the search results, which might slow performance if the user is allowed to sort and reorder the result list
- put the results in the page context (which is serialized to the client) - this can mean quite a lot of serialization overhead
On balance, my preference is for statelessness most of the time, but it depends upon the needs of your users. Session scope might be what they prefer (this is what we use in the Booking demo). Conversations don't usually have a place here.
STEP 2: When we navigate from a search screen to an item detail page, a similar choice space exists:
- keep the item in a new conversation
- stay stateless (request parameter and pull MVC)
- use the page context
Now the balance swings toward using a conversation, especially if we have some kind of complex object and need treeview-style navigation. (In this case, conversations give you transparent lazy association loading and all that good stuff.) But if bookmarkability is a requirement, statelessness is again the best choice.
Now we start getting to edit pages, association management pages, create/delete and the like. We are in the world of optimistic transactions, and so conversations rule. The main decisions revolve around conversation propagation: nested conversation? new conversation? stay in existing conversation? This depends upon how much the user wants to use
open in new tab!
Finally, wizards should always run in a conversation. In fact, it is highly likely that you want to use the
atomic conversation pattern where changes are kept transiently in memory on the server side until the conversation ends. Some wizards should use pageflows. (Remember I am using an extremely broad definition of
Hopefully this discussion helps the path from Disillusion to Insight, without scaring away the many people who have yet to experience the Rapture.