Programming Thoughts
Struts 2 - Post/Redirect/Get
Redisplaying a Form

Page designs aren't always simple

Struts 2 is a popular MVC framework for Java-based web applications. Its basic workflow is displaying a form for editing, processing a submitted form, and displaying the results but even something this straightforward requires some bespoke code.

The problem

Consider the screenshot of a simple form below. A viewer Action to display this will read the appropriate record from a database and the JSP page will construct the form to edit it. When a form is successfully submitted, the database is updated, and the browser can redirect to the viewer Action, which re-reads the database and displays an updated form. If the form submission fails, the browser redirects to the viewer Action, which redisplays a form based on the database record, not what the user submitted. This can frustrate users to the point where they refuse to use the application.

Figure 1: Form constructed from existing record

Examining the solution

The interceptors for preserving forms described in Post/Redirect/Get would appear to solve this problem but it can go wrong. It injects the form into the viewer Action before the execute function runs, which must realise it's received a form it shouldn't replace. That's more bespoke code for what's supposed to be straightforward workflow. The interceptor could inject the form after the invocation.invoke() line, so the viewer Action execute function is already done by that point but that means the Action wasted its time reading the database.

The problem is constructing the forms is particular to the viewer Actions, and can't be inherently known by the Struts 2 framework. This suggests a finer Inversion of Control design where the viewer Action supplies code for each form and an interceptor calls each except for the injected form. This is starting to sound like over-engineering and hard to read code.

Perhaps the problem should be ignored and the form injected after the viewer Action's execute but unnecessary database strain should be avoided. Database strain can be mitigated with caching but the tradeoffs of caching are peculiar to each application and can't be required by the framework.

Next part

Continued in Use ModelDriven, which explains reducing checks for injected forms to one simple if statement per form.