In this second post, a step-by-step approach will show how to construct a simple Grails-based workflow and integrate it with the Grails portal code created in the previous post. There are two parts to this post: the first one outlines the creation of the workflow; and the second shows how to use the results of the workflows in a business case.
By the end of this post the reader will be familiar with the issues around creating Grails Webflows and should be able to implement the workflow features of a Grails portal application. I have not included all of the classes referenced in the code examples in the text of this post, but they are located at the Google Code project for the Grails Portal : http://code.google.com/p/grailsportal/
Why use a workflow engine such as Grails Webflow?
Most web applications collect information in a stepwise fashion and then make business rule decisions based upon the data collected to accomplish some business functionality. This is a common requirement in any application, from a simple contact page to a commercial order management system. These types of requirements have been fulfilled in the past in many different ways.
When these applications were written in a green-screen (mainframe) environment, it was easy to handle the interactions between the user and the application. In those days, the user could not switch or close the application window (if there was a window) without indicating what to do with the data that was recorded. The modern web user interface makes managing these kinds of applications much more challenging because of these complexities:
· Workflow termination/re-entry:
This is the process by which the user determines the state that they would like to terminate the workflow. At any time a user can do any number of things to exit the workflow including but not limited to:
o Closing the Browser window
o Selecting the “Cancel” button
o Hitting the “Back” browser navigation button.
o Hitting the “Refresh” browser button
o Selecting the “Finish” button on the final state of the workflow.
o Using re-entrant workflow processing
· Workflow state:
This issues centers around the persistence of the state of the workflow throughout the “life” of the workflow. As the user progresses through the workflow there should never be a perception that the data associated with the workflow will not be saved. If the user purposefully exits the workflow, the user should also be allowed the option to save the data.
· Workflow implementation complexity:
Typically, using workflow frameworks are tedious and counterintuitive. Implementing flows with other frameworks included a combination of XML configuration files, visual representations, and tools that center around code generation.
Grails Webflow is a workflow based feature that gives the developer the tools to mitigate these issues:
· It uses the well known Spring Web-Flow framework to manage the workflow.
This framework handles the persistence of the workflow state and manages the issues when the refresh and back buttons are hit by the user during a workflow “session.” Combined with the Grails portal, it can also deliver targeted work flows based upon the user’s session state by recognizing specific values stored in the user’s web session.
· Its convention-based paradigm of Groovy simplifies the development process to implement the flows in a concise, understandable manner.
Flows are created in a manner consistent with other Grails development. No special tags are needed in the GSPs. Convention based development is used to connect the navigation on the workflow page to the workflow controller.
· Its native Grails domain classes make it simple to save the result of the workflow.
As in all other Grails development that accomplishes persistence of data, creating the code that saves and validates data is a chore but not as complex as in other Java-based frameworks.
What does the workflow do for the Grails portal?
Workflow for the Grails portal is one the fundamental user-based set of features that are used to write interactive web applications (apps that require users to log in and then provide individualized functionality). Workflow in a Grails portal is part of the launching pad for implementing functionality in a Grails portal. In Part I of this article, the setup of the project, domain objects, and security classes were created. In this article, the example will be further explained by demonstrating how to use Grails to manage appointments and create wizard pages to schedule appointments for the fictional wellness company “Wellness Unlimited.”
Grails Webflow definition
There are many types of workflow processes that exist, but it should be known that not all business flows lend themselves to being defined using Grails Webflow. There are three basic types of workflow:
1. Human-based workflow
This type of workflow is based upon the recording of activities strictly done by a human being where the intervals between recordings are extended and are not immediately ongoing. This can be, for example, the recording of a task such as a set of tests. Grails Webflow does not by default implement this type of workflow very well because it does not have the ability to re-constitute a workflow once the workflow session has been interrupted due to inactivity. It might be possible to implement this, but this is for future releases.
2. Computer-based workflow
This instance is where Grails Webflow shines. As in most web applications implemented using Grails Webflow, the intention is to collect data and guide the user between screens in one “flow session.” Typically, this works well for web applications where the workflow is completely computer based with no requirements to handle human based workflow.
3. Mixed workflow (computer and human)
A combination of the first two types, this hybrid workflow concept is a common reality when designing web applications. Grails Webflow can also implement this situation with a combination of traditional Grails web development and “linked” flows. In this case, the steps in the computer-based workflow steps are grouped together, and the human-based workflow is recorded separately from it.
The Grails portal example outlined in this article is an example of mixed workflow.
Planning is essential for success when defining a flow within a Grails application and all business processes can be distilled down to a basic set of steps. Whether you use a napkin or a formal diagram to document them, it is essential that you understand the required steps needed to implement the business process in your Grails application.
In Grails Webflow, you take each of the steps and create a Groovy closure to capture the functionality. If you follow the normal process of GSP and Spring MVC web application development, you will find there are some slight adjustments required to enable webflows to work. Also, you will see that in this example I have not enabled any dynamic run-time functionality; perhaps in a future article I will show how to accomplish this. In the example of the registration controller, each of the steps for the business process of registration is implemented by using standard procedures described in the Grails documentation for this product.
These procedures can be thought of as a roadmap for success. To start with here is a shortcut to understanding Grails Webflow development:
1. The name of the flow is described by adding the “Flow” word to the end of a closure in your controller.
2. When you create the views, you must use the same name as the webflow as a directory that contains the views for the webflow.
3. The views are contained in the named directory from the previous item.
4. Navigating between the webflows is pretty simple:
a. The “name” property of the submit button for the pages in the flow indicates which closure to execute when the page is submitted.
b. If the user navigates away from the flow, it is possible to re-establish the flow.
c. Once the user ends the flow by hitting cancel, you have the ability to manage the data within the flow.
5. Use the “flow” variable to store information for the span of a flow. This is a very important detail: without the flow variable you don’t have the ability to define the activity within a flow. I would recommend that you always use the flow variable when moving state within the flow.
6. The default context in a view used for a webflow uses the “flow” variable. Unless you have worked extensively with Grails you might not understand the concept of a set of domain objects that are put into the context, but the flow variable is a default variable put into the session context.
7. In your controller you should always have explicit entry points and exit points from the webflow. This can be implemented by creating a default closure that will be used by the webflow.
8. When designing your flow, account for the ability to cancel the flow at any time. Placing a “cancel” button at each point in the process is a good idea.
Implementing the flow in the Grails controller
Saving the results of the flow at each step is important, at least in my example. The domain object validation utility methods and GORM methods within Grails are used to save the data for each step in the flow. When writing the code in the main controller for the flow, these are the steps that are always followed:
1. Always use a service class when handling GORM objects. Either create a Grails service class or identify one that you will use to handle the saving of the data from the flow. For the example there are two service classes ContactUtilService and RegistrationService that are used explicitly for this purpose.
2. Create a method to move the data from the posted web parameters to the domain objects. This process can vary widely in complexity. One of the easiest ways to do this is to have the names of the items on the view the same as the ones in your domain class. If you do this you can just instantiate the class using the parms variable–it can be that simple!
3. Create a custom Java Exception class similar to ValidationException. Make sure that you have a try, catch block around the call to catch a validation error.
4. Write a method in service class to validate the domain object. If there is an error, throw a custom validation error that will show to the user the field that needs to be changed.
5. Handle the exception. If there is an exception, make sure the appropriate values are set in the view, and return the model.
6. If there is no exception, create the model objects needed and move the flow to the next page.
An Example of a flow in the Grails portal
In the example for this article, we save the result of each stage of the webflow via calls to service classes. The objective is to employ as much of the default functionality provided by Grails as possible. In the example below from the RegistrationController, the flow definition for collecting the data for the Wellness Unlimited registration is shown.
class RegistrationController{
…
…
registrationFlow{
def registrationService
…
…
enterRegistrantInfo{
on(GOTOPARENTINFO){
try{
flow.regEvent = this.registrationService.doRegistrationEvent(flow.regEvent,params)
if (flow.regEvent.parent1==null){
flow.regEvent.parent1=new RegistrationParent()
}
return success()
}catch(ValidationException e){
flash.message = "Please re-enter your information"
flow.registerInstance = e.getExceptionVal()
return error()
}
}.to ENTERPARENTINFO
on(GOTOCANCEL){
session.regEventId=null;
flash.message="Cancelled"
}.to CANCEL
}
…
}
Listing 1: The enterRegistrantInfo flow step
In this code snippet from the RegistrationController you see the call to the method registrationService.doRegistrationEvent() . There are some subtle but highly significant implementation details in this snippet of Groovy code:
a. Note that the reference to the service always uses a “this” reference. The instance variables at the class level must be referenced this way since the flow is part of a closure.
b. The custom exception ValidationException implements validation using the same patterns established by generated Grails scaffolding controllers and gsp files:
i. Setting the flash.message which shows this message at the top of the page.
ii. Setting the flash.registerInstance variable from exception, which is then used to generically
show the specific fields that need to be changed by marking them red.
Of course the visual representations of the error handling and validation can be replaced as they are accomplished using style sheets and images.
c. Both successful and unsuccessful calls to doRegistrationEvent enable a model necessary to render a view.
i. Make sure that when you return a view that you have the submitted values present in your flash scoped variable. This is important because there is nothing more unsettling for a user than an error where the page is returned–minus some of the originally submitted values.
ii. The approach that I recommend is to have the same flow variable used in each of the flow steps. This will lower the complexity, and allow you to carry the data from previous steps forward without having to re-query that database at each step.
Validation and persistence using Grails service classes
As you see in Listing 2, which is a code snippet from the RegistrationService class, throwing and catching exceptions as part of a flow is one of the many ways to use the default functionality of Grails and Java. When the exception is thrown, it’s most likely because of a validation error on the data that is entered. As you can see in the code below, the validate()method is executed which tests against the constraints specified in the domain class. For example, if a field does not have a value when it should (null) it will put the value into an errors() method on the domain object (RegistrationEvent) which is processed by the default functionality present in a GSP page that has been generated.
Grails service classes are somewhat unique in that they are the defined place within Grails development where database transactions through GORM are handled without much special work. Other methods in controller classes can do the same work, but in these classes much of the configuration for persistence is already done for you. In the doRegistrationEvent method you see an implementation of the instructions earlier in this article by:
1. Using GORM Validation for the domain objects
2. Keeping one single domain object (RegistrationEvent) that is used to return to the controller.
Class RegistrationService{
….
…
def RegistrationEvent doRegistrationEvent(RegistrationEvent regEvent,
params)throws ValidationException{
if (regEvent==null){
regEvent=new RegistrationEvent()
}
try{
regEvent.registrationAge=new Integer(params.registrantAge)
}catch(Exception e){
regEvent.registrationAge = 0
}
regEvent.registrationGrade=params.registrationGrade
regEvent.registrationDate=new Date()
def pt=PartyType.findByCd(CHILD)
regEvent.registrationFor=doParty(pt,"",regEvent.registrationFor,params)
SimpleDateFormat format=new SimpleDateFormat(DATEFORMAT)
regEvent.registrationFor.birthDate = format.parse(params.birthDate_year+
"-"+params.birthDate_month+
"-"+params.birthDate_day)
if (!regEvent.registrationFor.validate()){
throw new ValidationException(regEvent.registrationFor)
}
regEvent.registrationFor.save()
regEvent.registrationUser=this.securityService.getRegisteredUser()
if(!regEvent.validate()){
throw new ValidationException(regEvent)
}
regEvent.save()
return regEvent
}
…
…
}
Listing 2: doRegistrationEvent method in the registration service
Grails portal and web browser navigation
Another detail in this code snippet worth discussing is how the services class is controlling null objects which are a result of Web browser navigation. This is apparent consistently in the example above and all other service methods. Using a domain object to pass between the view, controller, and service layers creates situations where the flow will move back a step. If this happens, the user should see the data that they entered previously and when they submit an update, the service classes should seamlessly handle new objects as well as updates.
The Grails service part of the solution is partially apparent in Listing 2, which employs a simple solution to the problem. In the Grails portal example, the domain object regEvent of type RegistrationEvent is the object stored in the flow scope for the life of the flow. To process a change in this domain object, the regEvent object is passed in, regardless of whether the object has been instantiated or not. If it is null then it comes from a situation where the user has not yet created a registration in that flow. If the object is not null then it implies that the object already exists and we use the existing object. This is crucial because in each of these cases the returned object is the one that is used in the controller and then passed back to the view as the model.
Use of domain class configuration
In the example of Listing 3–which shows the RegistrationEvent domain class–configuration heavily influences the behavior of the Grails Portal. This is because the way that the domain objects are configured implies that it’s also the way that the objects are validated. Since this is how all objects in the Grails portal are validated from each step in the flow, it is essential to understand the issues around Grails domain model constraints, relationships, and mappings.
For conciseness, and the fact that all of the explanations for these options are available on the Grails wiki, only the significant behaviors that affect the Grails portal and may be useful for anyone modifying the code are included for this article.
· Grails constraints
o nullable: A simple concept that can really be an issue if set incorrectly. This indicates to the validation routine and the database creation scripts that this column should have a value (false), or it is OK for it to be false (true). Validation will fail if the constraint is set to “false” and the instance variable of the domain object does not have a value.
o inList: A restriction constraint that will check for these values when validation is run against this domain object.
· Grails mappings
o lazy: This is set to false for this domain class since this is the root object for the Wellness Unlimited registrations, and when a RegistrationEvent is retrieved from the database, it should return all referenced objects. By default, Grails sets this value to “true” for all GORM relationships.
import java.sql.Timestamp
class RegistrationEvent implements Serializable{
Party registrationFor
Date registrationDate
String registrationGrade
Integer registrationAge
EmergencyContact emergencyContact1
EmergencyContact emergencyContact2
EmergencyContact emergencyContact3
RegistrationParent parent1
RegistrationParent parent2
PickupContact pickupContact1
PickupContact pickupContact2
PickupContact pickupContact3
OrderRecord orderRecord
JsecUser registrationUser
RegistrationDoctor registrationDoctor
static constraints = {
registrationFor(nullable:false)
registrationUser(nullable:false)
registrationDate(nullable:true)
registrationGrade(inList:["Kindergarten",
"First",
"Second","Third",
"Fourth",
"Fifth","Sixth",
"Seventh",
"Eighth","Freshman",
"Sophomore",
"Junior","Senior"])
registrationAge(range:5..19)
emergencyContact1(nullable:true)
emergencyContact2(nullable:true)
emergencyContact3(nullable:true)
parent1(nullable:true)
parent2(nullable:true)
pickupContact1(nullable:true)
pickupContact2(nullable:true)
pickupContact3(nullable:true)
orderRecord(nullable:true)
registrationDoctor(nullable:true)
}
static mapping={
parent1 lazy:false
parent2 lazy:false
emergencyContact1 lazy:false
emergencyContect2 lazy:false
emergencyContact3 lazy:false
pickupContact1 lazy:false
pickupContact2 lazy:false
pickupContact3 lazy:false
orderRecord lazy:false
}
String toString() {
return registrationFor.firstName+" "+registrationFor.lastName
}
}
Listing 3: The RegistrationEvent domain class
Use of views in the Grails portal
Views have four main facets that, when combined, create what the user sees on the page represented by the GSP.
1. HTML notation
2. Grails tags
3. References to data available in different contexts
4. The GSP page template (not shown in this example)
The Grails portal employs a domain-based framework that takes advantage of the default scaffolding behavior of Grails. In Listing 4 the
In this post, each of the application layers of the Grails portal in respect to flow development has been detailed. From the view, controller, service layer and the domain, each contain a part of the puzzle to create an effective flow for a Grails portal. You should note that the files contain a full working and coded example of this functionality with these concepts fully employed.
The next and last installation of the Grails portal series will take the working application and apply management and user interface features such as Ajax to the foundation that has already been built. In addition, the process of deploying the Grails portal application into an existing Tomcat server environment will be covered.

0 comments:
Post a Comment