12:02 PM, Friday, June 16, 2006

Is the Portlet Programming Model Broken?

The two-phase life cycle model of portlets is a strange beast to explain to web developers. The most often heard complaint is that this model is quite unnatural for web developers. Why would any web developer want to split the simple GET and POST requests into "render" and "action processing" phases so that some portal can render it correctly? On the surface, this looks like a harsh requirement that portals impose on web app developers. A few months ago, one of my colleagues tried hard to convince me (unsuccessfully, of course) that the portlet programming model is broken, and that the portal guys got it completely wrong. I heard similar stories from portlet bridge developers whose main struggle is to some how bend over backwards to get JSF, Struts, and even ASP apps fit into the two-phase lifecycle of portlets. This exercise is easier described than done.

The key problem is not with the two-phase lifecycle model, but it is way it is understood and explained. As I try to argue below, the key principle is more fundamental, and has nothing to do with portlets and portals at all. What the two-phase lifecycle model dictates is a very well-known best practice for developing any web app.

The key design guidelines for developing web apps are the following

  • Keep as many resources as possible URI addressable, preferably with unique URIs
  • Use method GET to access idempotent representations of resources
  • Maximize use of method GET
  • When you must make state changes to a resource, use method POST. However, since POST is an unsafe request (i.e. not be repeated without explicit user intervention - see URIs, Addressability, and the use of HTTP GET and POST), redirect the client to point to an idempotent representation of the resource immediately after POST. See So Many Redirects - Which One to Use? for a discussion on the various kinds of redirects and the differences between those.

These guidelines provide (a) navigation-safety, i.e., users can use browser navigation controls (back, forward, reload etc.), and (b) bookmarkability. In essence, these guidelines make web apps plain and simple for users. I should mention that bypassing these guidelines usually causes clumsy user experience involving broken back buttons, non-bookmarkable URLs, duplicate POSTs etc., and runtime issues like lack of cachability, cache corruption (particularly with non-unique URLs), more expensive state management etc.

In the figure above, note the second step. Upon processing the POST request, the web app needs to encode the state of the resource in the redirect URI, so that the user can bookmark an idempotent representation of the resource. For instance, if the POST request is meant to create a user purchase order, you may need to encode the order ID and other pertinent details into the redirect location URI. In essence, such a redirect step would convert an unsafe interaction to a "safe interaction" that is safe to navigate, is repeatable, cacheable, and bookmarkable.

Now consider the two-phase lifecycle model of portlets as specified in JSR168 and WSRP 1.0. The life cycle has two distinct phases (a) an action processing phase to make state changes, and (b) a render phase to render the current representation of a portlet. Here is the mapping of these two phases onto the above figure.

No extra terms or boxes are required for this mapping. Of course, the terms used to describe the lifecycle are slightly different:

  • Action processing phase: This maps to non-idempotent operations like submitting a form via POST. In this phase you can make state changes. Just like POST, this phase is unsafe to repeat.
  • Render phase: This maps to the idempotent operation like getting the current state of a resource via GET. In this phase, portlets can't reflect state changes. Portlets must be prepared to be rendered many times without causing side effects.

What ties the action processing phase to the render phase is the navigational state. This is similar to the state you would encode in a redirect location after processing a POST.

Given this analogy, the two-phase style should seem more natural for building web apps - not just portlets. As long as web apps follow the POST + redirect style for processing non-idempotent requests, mapping of such web apps into portlets should be less difficult. The portlet programming model is not alien beast - it is just a natural representation of a well-known best practice. The fact that not all web apps follow this practice is a different issue. Adding insult to this injury, some frameworks like JSF got the idempotent vs non-idempotent concepts completely wrong. But that's a story for another time.

Comments

David Phipps said:

Interesting - what you describe here is the WSRP model of a portlet, rather than the model used by Plumtree Software (now BEA).

In Plumtree portlets, the portal stores all preferences, and also stores all links to key navigational elements in the portal application (such as preference pages). Navigation state is maintained by the portlet application itself; trying to stuff that nav data into a WSRP node, and maintain it through careful shepherding of what you send back in URL requests, is inherently a broken design.

The Plumtree model uses the concept of a "transformer" - literally, rewriting all links in the portlet application, including rewriting of javascript in the portlet application that forms links. This allows all link requests to be mediated by the portal automatically, without having to resort to recoding your application using an API. This mediation allows for pattern detection, which means you can detect when you've entered a "preference" page and therefore control what information you send to the portlet to use for rendering (i.e., user profile data, locale information, preferences and so on).

It also allows you to specify "gateway spaces", or other URL patterns (read: multiple domains) that get rewritten. This lets you, for instance, mix content from multiple websites into a single portlet. Traditional IT views this as a secuirty risk; standard web software development views this as an absolute requirement. The classic example is that of an imageserver: static images are served from one domain, while dynamic content is served from another domain.

The URL rewriting feature also menas you can click on links in the portlet, but configure the portal to use in-place refresh (AJAX) to update the content, allowing you to update only a single portlet instead of many in a page refresh.

The standard model described by JSR-168 and WSRP 1.0 actually represents a complicated mix of producer and consumer on each end of the conversation. The correct design would have one end being the one and only producer, responsible for its own content, and the the portal end being a consumer, responsible for accurate rendering of the content, regardless of what button is clicked in the portlet application.

Kirit said:

I've written about this issue myself at http://www.kirit.com/The%20%E2%80%9CCorrect%E2%80%9D%20way%20to%20process%20forms

I go into some details about when NOT to use it as well and which redirets to use for best effect. There are also things about error messages etc. that need some consideration as well as login forms.

I like the diagrams. Wish I'd thought of them too. I'll link to this from my article though.

Idempotent said:

Very invaluable blog post! I can't belive that I have been developing Web apps for more than a year without knowing this whole idempotent/non-idempotent thing!

I am badly waiting for your post on the wrong doings of JSF.

Best Wishes,
The Idempotent Guy (?)

Behi said:

Hi Subbu,

Man, when are you going to write about the fundamental problems of JSF?

-Behi

Behi said:

Hi,

Is it the responsibility of the Portal to handle Redirect-after-Post automatically or it is up to the user to implement this in the processAction!?

Could you please give your opinion about

http://my.opera.com/behrangsa/blog/show.dml/498527#comments

and

http://my.opera.com/behrangsa/blog/show.dml/501538

?

Best regards,
Behi

In the case of portlets, it is the responsibility of the portal or more accurately the aggregating app to redirect after POST.

Matt said:

Subbu, thank you very much for this nice overview of post-redirect-get in portlets. I assume that this is within one portlet, and that the ActionResponse.sendRedirect() in the processAction is redirecting to a render url of that same portlet. I can see this being a valid implementation of the PRG pattern.

However I do see a potential issue with cooperative portlets. Let's say portletA has a "POST" form, and in its action phase it uses the property broker to call portletB. portletB could do a redirect to its own render URL, but wouldn't the back-button in this scenario ultimately re-run portletA's POST, and get the "Warning: Page Expired" experience?

I'm curious enough now that I'll have to try it for myself, assuming you don't know off-hand.

Thanks again, this is the best source of info I've found on the PRG pattern in portlets.

Leave a comment