Abstract
Spring Web Flow is a component of the Spring Framework's web application stack that provides a simple way of writing stateful, conversational web applications. Spring Web Flow makes the logical flow in the web application a first class citizen by allowing you to define it as a self contained module that can be configured and reused independently of the rest of the application.
Spring Web Flow gives you new ways of developing stateful web applications by introducing various kinds of scopes for the stateful data; request, flash, flow and conversation. It also provides extension points for customizing how application state is managed.
Terracotta for Spring is a runtime that brings high-availability to Spring-based applications through clustering across multiple JVMs. The product provides direct support for transparent and declarative clustering of Spring Web Flows (as well as Spring beans in general) regardless of scope.
In this article we will first give you an overview of Spring Web Flow and Terracotta for Spring, and after that show you how you can use these technologies together to enter a new dimension in writing stateful, conversational, scalable and highly available web applications.
What is Spring Web Flow?
Spring Web Flow is a component in the Spring Framework's web application stack which provides a simple way of writing stateful, conversational web applications. Spring Web Flow makes the logical flow in the web application a first class citizen by allowing you to define it as a self contained module that can be configured and reused independently of the rest of the application. It is framework agnostic and can easily be used with the web application framework of choice, such as for example Spring MVC, Struts, or JSF.
The page flows are configured using a domain-specific language (DSL) specifically developed for the task of defining and composing page flows. Current implementations are XML and Java.
Spring Web Flow gives you flexibility and power when developing stateful web applications by introducing various kinds of scopes for the stateful application data; request, flash, flow and conversation that suit different use-cases and requirements.
Here is a quick summary of the most interesting features in the 1.0 release (from the release notes on InfoQ):
- Define all controller logic for an application task, such as a search process, in one place, instead of scattering that logic across many places.
- Compose flows together to create rich controller modules from simple parts.
- Enforce strict user navigation rules with a natural, object-oriented linear programming model and without coding verbose if/else blocks.
- Have memory you allocate during flow execution automatically clean itself up when the flow ends or expires.
- Deploy a flow for execution in a Servlet environment using your base web framework of choice.
- Change base web frameworks (e.g. Struts, Spring MVC, JSF, other) without having to change your flow definitions.
- Change environments all together, going from JUnit test to Portlet for example, without having to change your flow definitions.
- Evolve your application's navigation rules on-the-fly at development time without a container restart.
- Receive correct browser button support (back, forward, refresh) automatically with no custom coding.
- Store task data in any of four managed scopes: request, flash, flow, and conversation; each with their own distinct semantics.
- Test flows in isolation without the container. Ensure your application control logic works before you deploy.
- Visualize and edit your flow navigation logic graphically with Spring IDE 2.0.
Sounds interesting? So far it is only concepts and theory, but we will shortly look into how this all works out in practice. So, stay tuned.
The need for scale-out and high availability in the enterprise
Clustering is becoming increasingly important in the world of enterprise application development; developers continuously need to address questions like:
- How do we enhance scalability by scaling the application beyond a single node?
- How can we guarantee high-availability, eliminate single points of failure, and make sure that we meet our customer's SLAs (Service Level Agreement)?
Predictable capacity and high availability are operational characteristics that an application in production must exhibit in order to support business. Some companies require up to 99.9999 percent uptime in their application; others do not, but all applications need to remain operational for as long as the SLAs define and developing applications that operate in this predictable manner is just as hard at the 99.9% level as it is at 99.9999%.
Clustering has historically been a hard problem to solve. In the context of Spring Web Flow, and web applications in general, this in particular means ensuring high availability and fail-over of the user state in a performant and reliable fashion. In case of a node failure (the application server or JVM crashes), using sticky session (which is the most common and preferred way of configuring the load balancer), is a start but we also need an efficient way of migrating user state from one node to another in a seamless fashion.
What do we mean when we say clustering and how does it differ from caching? The definition of clustering that we use is: sharing the application state across multiple JVM's, while caching can be defined as: bring the application state closer to its execution context. In this sense, caching is a subset of clustering.
The minimal set of requirements that we think an enterprise-level clustering solution for web/enterprise applications should meet are:
- scalability
- high-availability
- fail-over
- performance
- minimal impact on existing code
- simple deployment and configuration
- runtime visibility (monitoring)
Let us focus now on a solution to all these seemingly disparate needs. There is always more than one way of solving a problem, and there are many products in the market that claim to provide high availability for web applications. Terracotta provides one such solution.
What is Terracotta for Spring?
Terracotta for Spring is a runtime for Spring-based applications that provides transparent and high performance clustering for Spring applications with minimal impact to the application code and with similarly little impact on the deployment and configuration process. It does this by clustering at the heap-level underneath the application instead of clustering the application directly.
It allows developers to develop single-node stateful Spring applications as opposed to stateless ones. This allows for scalable applications without having to design for clustering upfront. But when applications need to scale-out the application or ensure high availability and fail-over, they simply have to define which Spring beans in which application contexts they want to have clustered, in the Terracotta configuration file. Terracotta for Spring then makes sure that the application is clustered automatically and transparently and is guaranteed to have the same semantics across the cluster as on the single node.
In the case of Spring Web Flow it is actually even more simple; all the user needs to do in order to get their web application state and continuation repository clustered, is to declare the specific web application as having 'session-support' turned on in the Terracotta configuration file (see the section below, titled 'Declarative configuration', for details).
At a high-level view, Terracotta for Spring provides:
- Clustering of HTTP session state. Ensures high-availability and fail-over for Spring Web Flow user state and continuation repository, or any other state that is put into the HTTP session.
- Clustering of Spring beans. Life-cycle semantics and scope for Spring beans are preserved across the cluster - within the same "logical" ApplicationContext. The current clusterable bean types are singleton and session scoped beans. The user can declaratively configure which beans in which application contexts to cluster.
- Cluster POJOs transparently. No changes to existing code necessary, does not even require the source code. The application is transparently instrumented at load time, based on a minimal declarative XML configuration. Terracotta for Spring does not require any classes to implement Serializable, Externalizable or any other interface. This is possible since it does not use serialization, but is only shipping the actual deltas, the data that has changed, to the nodes that currently needs it (lazily).
- Virtual Memory management. It also provides distributed garbage collection and functions also as a virtual heap, for example, it can run a web application with 200 G heap on a machine with 4 G of RAM, since the physical memory is paged in and out on a demand basis. What this means is that you do not need to care about if the size of the conversational data that Spring Web Flow has exceeds the physical heap size.
Heap-level Clustering
Terracotta for Spring uses aspect-oriented technologies to adapt the application at class load time. In this phase it extends the application in order to ensure that the semantics of Java are correctly maintained across the cluster, including object references, thread coordination, garbage collection etc.
We also mentioned before that Terracotta does not use serialization. This means that any conversation maintained by Spring Web Flow can be shared across the cluster. What this also means is that Terracotta is not sending the whole object graph for the conversation state to all nodes but breaks down the graph into pure data and is only sending the actual "delta" over the wire, meaning the actual changes - the data that is "stale" on the other node(s).
Since it has a central coordinator (see below) that keeps track of who is referencing who on each node, it can work in a lazy manner and only send the changes to the node(s) that are referencing objects that are "dirty" and need the changes. This will make use of Locality of Reference, and will be even more effective if the load balancer is configured to use "sticky session", since it means that some data will never have to leave the actual session and be replicated to another node.
The architecture is hub and spoke based, meaning there is a central coordinator which is managing the clients. The client in this case is simply your regular application together with the Terracotta for Spring runtime. The coordinator is not a single-point of failure, but you can have an arbitrary number of coordinators standing by and upon failure the selected one will pick up right where the master coordinator that has crashed left off. This also allows you to scale the coordinator by clustering it independently of the clients.
Writing a highly available stateful Web application
Here we will use a sample application called Sellitem to drive the discussion and show you:
- How to write a stateful, conversational web application using Spring Web Flow.
- How to cluster the stateful application declaratively using Terracotta for Spring.
The Sellitem sample application is a demo that can be found in Spring Web Flow distribution (for more information, see the 'Resources' section at the end of this article).
Implementing a stateful web application using Spring Web Flow: Sellitem
Sellitem sample application demonstrates a mix of conditional transitions, conversational scope, flow execution redirects, and continuations. The user is guided through several pages that allows him/her to specify price the for an item, the number of items for sale, discount rates, shipping details (if required) and review all the information at the end.
We will not walk through all the source code for the application, but we will focus on explaining the key concepts of how to configure the different standard services (beans) that Spring Web Flow exports as well as how to define the page flow (using the XML version of Spring Web Flow's DSL for page flows).
Configuring the DispatcherServlet from Spring MVC
The entry point to this application is a standard DispatcherServlet from Spring MVC, that is registered in web.xml and mapped to *.htm resources in the web application context:
<servlet>
<servlet-name>sellitem</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/sellitem-servlet-config.xml
/WEB-INF/sellitem-webflow-config.xml
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>sellitem</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
The configuration of the DispatcherServlet resides in the Spring configuration file sellitem-servlet-config.xml and sellitem-webflow-config.xml. Inside sellitem-servlet-config.xml is a single Controller mapped to the "/pos.xml" URL that dispatches all requests to that URL to the Spring Web Flow system (whose entry point is a flow executor):
<bean name="/pos.htm" class="org.springframework.webflow.executor.mvc.FlowController">
<property name="flowExecutor" ref="flowExecutor" />
</bean>
Configure the flow executor and flow registry beans
The flowExecutor Spring bean is configured to use a flowRegistry bean to execute XML-based flow definitions located in the "/WEB-INF/flows/" directory.
<flow:executor id="flowExecutor" registry-ref="flowRegistry"/>
<flow:registry id="flowRegistry">
<flow:location path="/WEB-INF/flows/**-flow.xml" />
</flow:registry>
Define the page flow
The rest of the logic is defined in the flow definitions that we registered in the flowRegistry bean (see the 'Configure the Flow Executor and Flow Registry beans' section above).
Before diving into flow implementation details, lets first take a look at the graphical state chart diagram for the page flow (shown below).
Above we can see the flow goes through several steps before completing and there is a decision state that determines if shipping is required for the sale.
A good initial flow definition implementing just the navigation rules above (and no behavior yet) is shown below:
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">
<var name="sale" class="org.springframework.webflow.samples.sellitem.Sale"/>
<start-state idref="enterPriceAndItemCount"/>
<view-state id="enterPriceAndItemCount" view="priceAndItemCountForm">
<transition on="submit" to="enterCategory"/>
</view-state>
<view-state id="enterCategory" view="categoryForm">
<transition on="submit" to="requiresShipping"/>
</view-state>
<decision-state id="requiresShipping">
<if test="${flowScope.sale.shipping}" then="enterShippingDetails" else="finish"/>
</decision-state>
<view-state id="enterShippingDetails" view="shippingDetailsForm">
<transition on="submit" to="finish"/>
</view-state>
<end-state id="finish" view="costOverview"/>
</flow>
As we can see the definition above has states that correspond to the states of the state chart diagram, and transitions that correspond to the arrows in the diagram. The "sale" bean is a flow instance variable allocated when an execution of this flow starts. It is a holder for attributes about a sale.
The above definition has all the navigation logic flushed out, but has yet to implement any application behaviors. Specifically, the logic to update the Sale bean on user submit events has not been written. In addition, back-end sale processing logic has not been defined.
A completed Spring Web Flow definition implementing all required behaviors is shown below:
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=" http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">
<var name="sale" class="org.springframework.webflow.samples.sellitem.Sale"/>
<start-state idref="enterPriceAndItemCount"/>
<view-state id="enterPriceAndItemCount" view="priceAndItemCountForm">
<render-actions>
<action bean="formAction" method="setupForm"/>
</render-actions>
<transition on="submit" to="enterCategory">
<action bean="formAction" method="bindAndValidate">
<attribute name="validatorMethod" value="validatePriceAndItemCount"/>
</action>
</transition>
</view-state>
<view-state id="enterCategory" view="categoryForm">
<transition on="submit" to="requiresShipping">
<action bean="formAction" method="bind"/>
</transition>
</view-state>
<decision-state id="requiresShipping">
<if test="${flowScope.sale.shipping}" then="enterShippingDetails" else="processSale"/>
</decision-state>
<view-state id="enterShippingDetails" view="shippingDetailsForm">
<transition on="submit" to="processSale">
<action bean="formAction" method="bind"/>
</transition>
</view-state>
<action-state id="processSale">
<bean-action bean="saleProcessor" method="process">
<method-arguments>
<argument expression="flowScope.sale"/>
</method-arguments>
</bean-action>
<transition on="success" to="finish"/>
</action-state>
<end-state id="finish" view="costOverview">
<entry-actions>
<action bean="formAction" method="setupForm"/>
</entry-actions>
</end-state>
<import resource="sellitem-beans.xml"/>
</flow>
Now in addition to defining navigation logic, the flow defines actions to invoke appropriate application behaviors at the right times. This includes logic to process user submit events and invokie a backend sale processor to process the sale.
Form binding and validation
When entering a view-state that displays a form, the flow calls a FormAction command bean to execute form setup and submit logic. On submit, the FormAction binds user request parameters to the proper sale attributes where it may also validate them.
<bean id="formAction" class="org.springframework.webflow.action.FormAction">
<property name="formObjectName" value="sale"/>
<property name="validator">
<bean class="org.springframework.webflow.samples.sellitem.SaleValidator"/>
</property>
</bean>
For more information
Complete code and documentation on Spring Web Flow and its 10 sample applications (including Sell Item) can be found on the Spring web site at http://www.springframework.org/webflow.
Clustering the Sellitem application
So, now we have seen how to implement a conversational web application using Spring Web Flow. Now let's have some more fun and take a look at how we can enable high-availability and failover for our sample application by seeing how it can be clustered with Terracotta for Spring to provide transparent fault-tolerance and share state across the cluster nodes.
Sounds hard? Well, if it actually turns out to be quite simple.
Declarative configuration
The Sellitem sample application is using an instance of the Sale class to store all conversation data for a current sale; also, Spring Web Flow's flow execution repository is using the HTTP session to store all conversation data.
In order to enable Terracotta for Spring we need to specify that HTTP session clustering is enabled for a given web application and include all the classes, that can potentially be stored in the HTTP session (or be reachable from an instance that is stored in the session), for instrumentation. Here is a sample of what the Terracotta for Spring's tc-config.xml configuration file can look like:
<application>
<spring>
<jee-application name="swf-sellitem">
<session-support>true</session-support>
<instrumented-classes>
<include>
<class-expression>
org.springframework.webflow.samples.sellitem.Sale
</class-expression>
</include>
</instrumented-classes>
</jee-application>
</spring>
</application>
Here we have enabled HTTP session clustering for the swf-sellitem WAR file and included the Sale class for instrumentation.
That's it, now we are pretty much done.
Enable Terracotta
The only thing left that we need to do is to enable the Terracotta for Spring runtime for our application. This is done by modifying the Tomcat web server's startup script and adding the following environment variables to the top of the script:
set JAVA_OPTS=-Xbootclasspath/p:"%DSO_BOOT_JAR%"
set JAVA_OPTS=%JAVA_OPTS% -Dtc.install-root="%TC_INSTALL_DIR%"
set JAVA_OPTS=%JAVA_OPTS% -Dtc.config="%LOCAL_DIR%\tc-config.xml"
In which:
- DSO_BOOT_JAR environment variable is set to the location of boot jar (can be found in the common/lib/dso-boot directory in the root folder of the Terracotta for Spring installation).
- TC_INSTALL_DIR environment variable is set to the Terracotta for Spring installation root directory.
- LOCAL_DIR is set to the folder that contains tc-config.xml.
Sellitem application preconfigured for clustering with Terracotta for Spring is available from the 'Resources' section below. It also contains configuration for Tomcat cluster that can be started out of the box.
Note: when clustering beans in the Spring application context we can work on service (bean) name level and rely on Terracotta for Spring's auto-include detection mechanism, e.g. in most cases we do not need to care about which classes to include but only define the beans we want to cluster by specifying their name in the tc-config.xml file.
Summary
Spring Web Flow provides a great way to write stateful, conversational web applications ranging from simple applications like the one we have seen in this article, to large scale enterprise applications with many rich page flows. Terracotta for Spring brings high-availability to your Spring Web Flow applications.
In short, Terracotta for Spring provides:
- Fault-tolerance for Spring Web Flow-based applications, including regular Spring applications.
- Transparent sharing of the state for the application across multiple distributed nodes with no need to implement java.lang.Serializable
- Coordination of resources is maintained across multiple distributed nodes
- Pass-by-reference semantics is maintained across multiple distributed nodes
- Declarative configuration with almost no changes to existing code (except where Spring applications are explicitly stateless and need to instead become stateful)
Together, Spring Web Flow and Terracotta for Spring give you a new dimension in writing stateful, conversational, scalable and highly available web applications.
Resources
Spring Web Flow
Main Site, Download, and Documentation: http://www.springframework.org/webflow
Demo application: The Sellitem application that we have walked you through in this article is shipped with the Spring Web Flow distribution.
Try out the Sellitem application online: http://spring.ervacon.com/swf-sellitem/.
Terracotta for Spring
Terracotta for Spring a part of the Open Terracotta project which provides Open Source JVM-level clustering: http://terracotta.org/.
Download: http://terracotta.org/confluence/display/orgsite/Download
Demo application: The Terracotta for Spring distribution is shipped with a simplified version of the Sellitem application that is pre-configured (it even has Tomcat and a load-balancer bundled) and can be run directly as-is.
Documentation: http://terracotta.org/confluence/display/docs1/Spring+Quick+Start.
About the Authors
Jonas Bonér is working at Terracotta Inc. with a focus on strategy, product development & architecture and technical evangelism. He is the founder of the AspectWerkz AOP framework, has been a committer to the Eclipse AspectJ 5 project and various other open source projects and is a frequent speaker on AOP, JVM-level clustering and other emerging technologies.
Eugene Kuleshov is an independent consultant. He has over twelve years of experience in software design and development and specializing in application security, enterprise integration (EAI) and message oriented middleware. He is an active contributor to various projects in the Open Source community.