UI-Component Sets
Project Documentation

Best Practice

This page contains information on how the Orchestra developers believe Orchestra is best used. As always, best practices evolve - see the wiki for the latest information.

Single Page Conversation With Access Scope

This applies when you have a single page that the user may submit multiple times (eg select an item from a list and click a button, then select a different item and click another button). The state of the page changes after each submit, but this state must be held in memory.

Write a single managed bean for the view. Configure this bean as belonging to an unnamed access-scoped conversation and reference it as normal from your presentation pages (jsp, facelets, clay, etc).

The conversation will be started when the bean is first accessed, and will automatically be terminated (ie the bean will be deleted) when navigation to some other page occurs (well, technically when a page is rendered that does not reference this managed bean). If you have an action method that wants to "start fresh" while remaining at the same view, then from the action method call ConversationUtils.invalidateAndRestartCurrent() to discard the current conversation.

If this bean is named to match the view that it is backing (eg beanname="edit" for the "/edit" viewid), then it is trivial to get "lifecycle events" from Orchestra's ViewController. Even if the bean-name does not match the view, there are ways of configuring things so it still receives the appropriate callbacks. See the ViewController documentation for more information on this.

Multi Page Conversation With Access Scope

This applies when you have a set of pages that cooperate together to interact with the user, but where the state used by these pages must be held in memory. Commonly, sequences of pages like this eventually lead to a "save" or "execute" button which then performs an action using the state that was set up by the user via the preceding pages. Such sequences are sometimes called a "wizard".

Use one access-scoped controller for the whole conversation, plus one simple request-scoped or access-scoped bean per view. For example, if you have three pages then structure things as follows:

  • Page:purchaseStep1.jsp
    Bean name: purchaseStep1
  • Page:purchaseStep2.jsp
    Bean name: purchaseStep2
  • Page:purchaseStep3.jsp
    Bean name: purchaseStep3
  • Conversation bean name: purchaseController

Generally, there is no need to specify a conversationName attribute for these beans, ie the name of the conversation they are in is the same as the name of the bean.

The per-view beans handle logic and state that is specific to that page. If there is state needed for the page, then use "access" scope, otherwise use "request" scope. Inject the controller bean into each per-view bean so that state and logic which is shared between views can be accessed.

If there is common logic that each page shares, then that can be defined in an abstract base bean which the per-view beans extend.

EL expressions in the pages can reference either the per-view bean or the common controller bean, whichever is appropriate.

There are two problems with workflows:

  • A conversation may timeout in the middle of a conversation (eg the user goes to lunch, then tries to continue on return), and

  • A user may try to leap into the middle of a conversation (eg via a bookmark)

With the above recommended structure, each per-view bean except the first one can then check whether the conversation exists and is in an appropriate state for that view. If not, then a navigation to the proper "entry page" for the conversation can be done.

The basic check for the existence of the conversation is fairly simple to do in java code:

public void initView()
{
    ConversationUtils.ensureConversationRedirect("purchaseController", "/purchaseStep1.jsf");
}
          
If a set of per-view beans share a common base class then this initView method can be added to the base class, then overridden only in the page that is redirected to ("purchaseStep1.jsp" in this example) to prevent circular redirects. This then protects all of the pages from access without a correctly initialised conversation.

There is one issue: after redirect it would sometimes be nice to display an error message indicating *why* the redirect happened, eg "Invalid Conversation State". This is not easy on a redirect. A forward would be easier to handle, as that data could be placed in request scope.

Without this check, when a user leaps into the middle of a conversation, EL expressions will trigger the creation of the missing purchaseController (and its associated conversation) but the bean probably does not have the appropriate state to render the page correctly.

The orchestra core15 module provides the @ConversationRequire annotation to make this even easier.

Note that this works even when the purchaseController bean (which is in the purchaseController conversation, unless otherwise configured) is injected into the per-view bean. The object injected is actually a proxy, so the conversation is not created until the bean is really referenced.

Notice: There is also a JsfConversationUtils-class which allows you to invoke a navigation rule rather than encoding a url in the call to method ensureConversationRedirect.

If the views in a workflow are so simple that there is no logic or state needed, then rather than declaring "dummy beans" just to receive lifecycle callbacks the way the ViewController maps viewids to beannames can be configured. In particular, see the ViewController annotations from the Orchestra core15 module. Having a separate bean for the "entry page" of a workflow is always a good idea however.

Multi Page Conversation With Manual Scope

In some cases a conversation should not terminate until page N has been visited, but in the middle a user can go off and visit a page that has no references to any managed beans within the main conversation. When using Access scopes, Orchestra will interpret a visit to such as page as the user "abandoning" the conversation, so the conversation will be discarded. In this case, use a manual scoped conversation, ie one that must be explicitly ended via either a JSF component (ox:endConversation) or a call to the Orchestra API from an action method. Use access scopes where possible, though - they are less work.

Avoid Session Scope

In almost all cases, using Session scope is a bad idea. All sorts of data goes into sessions, including data from UI frameworks, and sometimes from the servlet engine itself. Instead of using session-scope, put all such beans into a single conversation scope called "session". The most significant benefit from this is that Orchestra's "conversation context" feature now allows a user to open multiple windows to your app without problems; each window has a different "conversation context", and each "conversation context" has a completely independent set of conversations - including all the beans in the "session" conversation. It's almost like the user connecting from two different machines - except that any application login (authentication) data is shared.

There are a few places where real session-scoped data might be appropriate, but not many. Think whether two separate browser windows for a user really should share that data. And if they should, then make sure that the shared objects are thread-safe, as two concurrent requests from two different windows will be using the same instance.

Component bindings

We recommend you read about component binding and the scoping problem. This document is not specifically about Apache MyFaces Orchestra, but the same issues apply when dealing with component-bindings and conversation-scoped beans.