Skip to content

Wonderful WebFlow

A few projects ago, I had the pleasure of using the early versions of Spring WebFlow.
For the great unwashed out there, WebFlow is:

…a component of the Spring Framework’s web stack focused on the definition and execution of UI flow within a web application.

The system allows you to capture a logical flow of your web application as a self-contained module that can be reused in different situations. Such a flow guides a single user through the implementation of a business task, and represents a single user conversation. Flows often execute across HTTP requests, have state, exhibit transactional characteristics, and may be dynamic and/or long-running in nature.

In other words, WebFlow makes it easy to create and control long-running, multi-page ‘Wizards’ that hold a stepwise dialog with the user.

Using WebFlow was straightforward and resulted in a system that was clean, well-structured and efficient…I wrote about this on the WebFlow forum, noting:

Here is my experience: “it all just works!” And well :-)

Webflow gave me no heartache at all.

WebFlow is normally configured via an XML file that tends to get pretty large pretty quickly. Tools like Spring IDE and the Spring Web Flow Visual Editor built into IntelliJ do help control the complexity but it seems as though the tide is turning against XML (something that I expect we’ll all live to regret one day, but this industry seems to have no true “organisational memory”: we just don’t learn lessons…); maybe its the GenY-ers amongst us looking for instant gratification?

In this spirit then, this posting is all about how Grails makes living with WebFlow easier.

The project is a simple calculator: enter an operand on one page (with one form), enter a second operand on a second page/form, enter an operator on a third page/form and display the result on a final page.

It’s not beautiful, I know! I’m only interested in the ‘core’ aspects of the application here…

The application flow allows for going back to revist pages as well as simply moving forward. Properly allowing for backward motion along is a non-trivial task, typically requiring
server-side and/or client-side state maintenance and continuation support.

In Grails, WebFlow support is a standard feature available to any controller and is configured conventionally by defining a closure with a name ending in ‘Flow’…take a look:

class CalcController {

  def totalFlowsCreatedSequenceMBean
  def instantaneousFlowCountMBean

  def index = {
    redirect(action: 'calc')
  }

  def calcFlow = {
    startup {
      action() {
        flow.flowCreatedSequence = totalFlowsCreatedSequenceMBean.increment()
        log.debug "calcFlow startup; this is flow #${flow.flowCreatedSequence}; instantaneous flow count: ${instantaneousFlowCountMBean.increment()}"
      }
      on('success').to 'init'
    }

    init {
      action() {
        flow.op1 = new OperandCommand()
        flow.op2 = new OperandCommand()
        flow.oper = new OperatorCommand()
        flow.res = null
      }
      on('success').to 'operand1'
    }

    operand1 {
      on('next') { OperandCommand cmd ->
        flow.op1 = cmd
        !flow.op1.validate() ? error() : success()
        log.debug "OP1: $flow.op1.value"
      }.to 'operand2'
    }

    operand2 {
      on('back').to 'operand1'
      on('next') { OperandCommand cmd ->
        flow.op2 = cmd
        !flow.op2.validate() ? error() : success()
        log.debug "OP2: $flow.op2.value"
      }.to 'operator'
    }

    operator {
      on('back').to 'operand2'
      on('next') { OperatorCommand cmd ->
        flow.oper = cmd
        !flow.oper.validate() ? error() : success()
        log.debug "Operator: $flow.oper.operator"
      }.to 'calculate'
    }

    calculate {
      action {
        def left = flow.op1.value
        def right = flow.op2.value
        def operator = flow.oper.operator
        switch (operator) {
          case '+': flow.res = left + right; break;
          case '-': flow.res = left - right; break;
        }
        log.debug "${left} ${operator} ${right} = ${flow.res}"
      }
      on('success').to 'shutdown'
    }

    shutdown {
      action() {
        log.debug "calcFlow shutdown; this is flow #${flow.flowCreatedSequence}; instantaneous flow count: ${instantaneousFlowCountMBean.decrement()}"
      }
      on('success').to 'results'
    }

    results()
  }
}

class OperatorCommand implements java.io.Serializable {
  static constraints = {
    operator inList: ['+', '-']
  }

  Character operator
}

class OperandCommand implements java.io.Serializable {
  static constraints = {
    value nullable: false
  }

  Integer value
}

This Controller intiates a flow in response to being invoked via the url http://…/Calc/calc/calc.
(Application context = ‘Calc’, controller name = ‘calc’, flow name = ‘calc’…it does make sense, really!)

It is much easier to read this WebFlow DSL than to read the equivalent XML configuration. Trust me! Since Grails is based on Groovy and can use closures, the WebFlow DSL also thankfully frees one from the need to create many, many small action classes (in an earlier project I had to create about 100 classes, most of which had one line of ‘real’ code in them. Traditional WebFlow is unfortunately a bit smelly in this regard; creating a class hierarchy helped rein in some of the nastiness and creating a parent-child relationship in the XML config file also helped, but the Grails DSL is just so much better!).

Rather than walk laboriously through each line, consider only the ‘operand2′ closure in the flow. It is easy to see how the application behaves at this point (and indeed, this is the point of most DSL: easy comprehension): when the ‘back’ event ocurs, move to the ‘operand1′ state and show the corresponding view, which–by Grails’ WebFlow convention–is found in the file ‘operand1.gsp’. When the ‘next’ event occurs, execute the corresponding closure before transitioning to the ‘operator’ state.

Note how the transition associated with the ‘next’ event has an associated closure that obtains and validates the associated posted form data as the associated custom-defined Command object is stored into ‘flow’ scope. Flow scope is a WebFlow ’special’ analogous to ’session’ scope but which–unsurprisingly–only exists for the duration of the flow.

How are the next/back events raised to the controller? They are generated as a response to the user interacting with the application and are specified in the corresponding ‘operator2.gsp’ view GSP. In this case, the events are generated by/correspond to the two buttons associated with the form:

...
<g:form action="calc">
  <label for="value">Operand 2:</label>
  <g:textField name="value" value="${op2?.value}"/>
  <br/>
  <g:submitButton name="back" value="Back"></g:submitButton>
  <g:submitButton name="next" value="Next"></g:submitButton>
</g:form>
...

Under the covers, the generated form becomes:

...
<form action="/Calc/calc/calc?_flowExecutionKey=_c06FB562D-5D87-0B93-A938-465ABAFAB026_kA42D2D04-3B12-C13D-B207-E68814B81AFC" 
          method="post" >
  <input type="hidden" name="_flowExecutionKey"
             value="_c06FB562D-5D87-0B93-A938-465ABAFAB026_kA42D2D04-3B12-C13D-B207-E68814B81AFC" id="_flowExecutionKey" />
  <label for="operator">Operator:</label>
  <select name="operator" id="operator">
    <option value="+" >+</option>
    <option value="-" >-</option>
  </select>
  <br/>
  <input type="submit" name="_eventId_back" value="Back" id="_eventId_back" />
  <input type="submit" name="_eventId_next" value="Next" id="_eventId_next" />
</form>
...

It should now be clear how the events are raised and sent to the controller.

OK, so how does the server know what particular flow the generated events correspond to? The server generates and maintains a flow-unique identifier (similar to the well-known JSESSIONID) called ‘_flowExecutionKey.’ By looking at the “under the covers” code above, it is easy to see that all flow-related requests will contain this identifier.

If you can see the correspondences between flow/transition/view/form element/Command object, you should be able to see how the whole thing hangs together.

It should now be clear that there is a fair bit of work going on under the covers: the server has to maintain the current state of a flow on behalf of the user, it has to be able to parse an action out of an incoming request and determine an appropriate transition and action closures, the correct HTML has to be generated, etc. Of course, there is also always the possibility that some exceptional circumstance may occur and this needs to be dealt with in ’sensible’ fashion as well. I’m ignoring exception handling for this posting (what a cop-out, eh!); it’s not difficult but we’ve got enough to be going on with here.

If you had to build equivalent functionality “by hand” you’d find yourself with a fair-sized project at hand, with all that implies for quality, etc. It as also very doubtful that the hand-crafted attempt would be as clean and expressive as Grails’ DSL version.

WebFlow is one of those features (along with Groovy’s inherent and unbeatable interoperability with Java, of course) that moves Grails away from being only suitable for small-scale “tiny apps” to being truly enterprise-grade technology.

Give it a go!

Tags: ,

C, Java Enterprise Edition, JEE, J2EE, JBoss, Application Server, Glassfish, JavaServer Pages, JSP, Tag Libraries, Servlets, Enterprise Java Beans, EJB, Java Messaging Service JMS, BEA Weblogic, JBoss, Application Servers, Spring Framework, Groovy, Grails, Griffon, GPars, GAnt, Spock, Gradle, Seam, Open Source, Service Oriented Architectures, SOA, Java 2 Standard Edition, J2SE, Eclipse, Intellij, Oracle Service Bus, OSB