Taking some cues from MyFaces (particularly the popularity of the x:saveState tag) and from a few books and articles published since the Pilot, we developed an experimental set of guidelines for JSF design, along with a new component to support them.
Here are our best-as-we-could-figure-out practices:
- Every command meant to return to the same page is handled by an "actionListener". Only commands that are expected to send the user to another page are handled by "action" attributes.
- Use a PhaseListener when debugging. This proved extraordinarily helpful.
- Every page has its own backing bean. No sharing of backing beans themselves between JSP pages. When we start to see code re-use, we refactor into a class hierarchy or helper classes, or write a JSF tag.
- Each backing bean splits the role of view and controller. We try to keep those two aspects cleanly divided in the code. (In a way, it seems cleaner to split them into different classes. But when I tried it, that created more work than it eliminated.)
- Each backing bean is Serializable. The serializable fields consist only of what JSF needs to manage the component model, including input fields and context variables. Everything needed to recreate the view can then be maintained across requests (courtesy of the gbx:flowState tag).
- Initialize selection IDs via the f:param tag. Until another entry link is clicked, they're then managed by the request-thread state-saver. This gives us a simple way to tell when a new request thread starts.
- Confine DB queries which set up the page to the bean's "init()" method. In other words, data loading is not done in action handlers, in getters, etc. This ensures that we load our bean only after all the criteria we need is in place, and prevents the "retrieve it and then throw it away" redundancies and "where did that object go to?" surprises we kept having during the pilot.
- Avoid unnecessary DB queries. In particular, we may selectively avoid refreshing from the DB for work-in-progress request-threads (such as the "what if?" support of the Feedback Options page) or when dealing with a validation error.
- Keep domain classes out of tag references and the serialized backing bean. That is, complex persisted objects such as "Assignment" or "GradeRecord" don't show up in input fields, "rendered" or "disabled" attributes, table definitions, etc. Instead, we explicitly copy any fields we need to and from the business object. IDs rather than whole objects are used to coordinate with the DB.
This last guideline is the one I feel least certain about. It requires more initial code and more maintenance than directly referring to the domain objects, and it forces the backing bean to more closely track changes to the page and business logic. The benefits are: 1) The bean explicitly controls what's on the page. 2) The domain classes don't have to be serializable. (If we serialized the Assignment Grade Record as it's currently defined, it would pull in its referred-to GradableObject, which in turn would pull in its referred-to Gradebook....) The first benefit was the one I was most interested in, since our goal was predictability. The second may be bogus, since we're being much more cautious about direct links and collections than we were in the Pilot. We still wouldn't want to serialize all data that feeds into the Roster page, but on some other pages it might be useful.