Skip to main content

Business Object State Management Using State Machine Compiler

{cs.r.title}









Contents
Introduction
Defining State Transitions
Generating State Machine Classes
Binding State Machine to Business Objects
An Architecture Using SMC
Conclusion
Resources

Business objects are often the most important pieces of an
application. They are persistent and long-lived, and often have
more than one state. At each state, the business behavior may be
different. The business logic implementation can become complicated
and hard to test and maintain because the business object state
transition logic is scattered in the business logic, often using
switch statements. The State
Machine Compiler
(SMC), a Java-based open source tool, can be
employed to decouple the business object state transition logic
from the business logic. It leads to simple, easy-to-test, and
reusable business logic implementations, especially for business
object-centric applications.

In this article, after a brief overview, we will see how to
define state transitions and how to use XML files to make the
definitions reusable and easy to manage. We also discuss how to
generate a state machine and bind a state machine to a business
object. At the end, we will briefly describe an architecture using
SMC.

Introduction

SMC is an open source tool that allows us to centralize object
state transition definitions in state machine definition files. SMC
uses these state machine definition files to generate finite state
machine Java sources. Besides Java, it can generate state machine
source code in languages such as "http://www.ruby-lang.org/en/">Ruby, "http://msdn.microsoft.com/vcsharp/programming/language/">C#,
C++, Python, and "http://www.perl.org/">Perl.

Let's first review some basic terminology that we will use in
this article. Assume we have a business object, Order,
for an online store application. The state diagram for the business
object is shown in Figure 1.

State diagram for the Order object
Figure 1. State diagram for the Order
object

The Order object has four states:
Created, CheckingCredit,
Filled, and Shipped. The transitions for
the Order object include view,
submit, reject, fill, and
ship. A transition may have guard conditions. For an
example, isCreditBad is the guard condition for the
reject transition. The guard conditions must be met in
order for a transition to proceed. There may be one or more actions
in response to a transition. doFill is the action
for the fill transition. In addition, we can specify
entry and exit actions, which will be executed when the object
enters and exits a state, respectively.

A typical usage of SMC involves the following steps:

  1. Define states and transitions in the state machine definition
    files.
  2. Compile the state machine definition files to Java
    sources.
  3. Implement the transition actions and guard conditions.

Let's see how to define states and transitions in the state
machine definition files.

Defining State Transitions

The SMC .sm file is the central place to define the
states and transitions for a business object. Here is the state
machine definition for the Order object:


//
//file: OrderWO.sm
//
%class OrderWO
%package com.lizjason.smc
%start MainMap::Created
%map MainMap

%%
Created{
    submit(userId:String, itemID:String, itemCount:int)
        CheckingCredit {}
    view() nil {}
}
CheckingCredit{
    reject()[!ctxt.isCreditOK()] Created {notifyCustomer();}
    fill()[ctxt.isCreditOk()] Filled {doFill();}
}
Filled{
    ship() Shipped {notifyCustomer();}
}
Shipped{
}
%%

The .sm file syntax detail is available in the "http://smc.sourceforge.net/SmcManual.htm">SMC documentation.
Here is the brief description of the syntax used in this
example:

  • The %class keyword specifies the business object
    class with which this state machine is associated.
  • The %package keyword tells where to put the
    generated state machine class.
  • The %start keyword defines the start state of the
    state machine.
  • The %map keyword specifies the name of the state
    machine.
  • Transition definitions are specified inside of the map, which is
    demarcated by the %% delimiter. Each transition has a
    start state and a next state. Optionally, a transition
    may have guard conditions and actions. As an example,
    the start and next states for the reject transition are
    CheckingCredit and Created, respectively. notifyCustomer is the transition action and
    ctxt.isCreditBad is the guard condition. Note that
    ctxt means the business object this state machine is
    associated with; it is the Order object in this
    example. Also, nil indicates a loopback
    transition.

A transition, as shown for the submit transition,
may have arguments. The same is true for the guard conditions and
actions. For real-world core business objects, which often have
many transitions, this .sm file can become unwieldy and hard
to read. To make the state transition definition reusable and easy
to manage, we can modularize each state transition as an
independent XML file and then assemble the modularized state
transitions, by using XML external parameter entity references and
external entity references, into a master XML state definition
file. After that, we can transform the master XML state definition
file into a .sm file, which SMC can use to generate a state
machine Java class. As an example, the fill transition
definition (fill_Filled.xml) is as follows:


<transition>
    <name>fill</name>

    <guard-condition>
        ctxt.isCreditOK(ctxt.getOrder())
    </guard-condition>

    <next-state>Filled</next-state>

    <actions>
        <action>doFill(ctxt.getOrder())</action>
        <action>
            updateState(ctxt.getOrder(),
                OrderState.Filled)
        </action>
    </actions>
</transition>

The updateState action is used to synchronize the
business object state to the current state of the state machine. It
is not a real business action and should be handled differently in
real-world applications. Once individual transitions have been
defined, we can assemble the master state machine definition via
XML entity references. The state machine for the Order
object is defined as follows:


<?xml version="1.0"?>
<!--
description: The state machine definition for
             the order object.
-->
<!DOCTYPE smc SYSTEM "OrderWO.dtd">
<smc class="OrderWO" package="com.lizjason.smc">
    <imports>
        <import>com.lizjason.smc.*</import>
    </imports>
    <start-state map-name="MainMap"
        state-name="Created"/>
    <maps>
        <map name="MainMap">
            <state name="Created">
                &submit_CheckingCredit;
                &view_Created;
            </state>
            <state name="CheckingCredit">
                &reject_Created;
                &fill_Filled;
            </state>
            <state name="Filled">
                &ship_Shipped;
            </state>
            <state name="Shipped">
            </state>
        </map>
    </maps>
</smc>

Note that a transition reference name convention is used here.
For an example, ship_Shipped indicates it is a
ship transition and the next state is
Shipped. If, say, the view transition
applies to all of the states, we can simply add a view
transition reference to each state instead of copying and pasting
the actual transition definition. OrderWO is the
"wrapper object" (or "worker object") to serve business service
requests for the Order business object. It is created
on the fly by the state machine factory. Essentially, it
encapsulates the state machine (including the implementations of
the actions and guard conditions) and the business object. It is
the "owner" of the state machine. The Order business
object itself, however, is independent of the state machine.

It is possible that we need to define multiple state machines
for a business object type. As an example, if the
CheckingCredit state does not apply to the orders
placed by the members of the store, we can define an additional
state machine for the Order object. In this case, we
can reuse all of the shared transition definitions through XML entity
references.

Generating State Machine Classes

After the XML-based state machine definition file is transformed
to the corresponding .sm file, we can compile the .sm
file using the following command:


java -jar Smc.jar -java Order.sm

The command option -java specifies Java language
output.

SMC can also generate state diagrams from the .sm files.
But for real-world core business objects, which often have many
transitions, the generated state diagrams are too crowded to be
readable. With XML-based definition files, we can transform the
state definitions to well-formatted state transition tables.
Developers and domain experts can use the transition tables to
review the business object state transitions without resorting to
the implementation or the design-phase-created state diagrams,
which are easily out of synch with the implementation. Since the
transition tables and the state machine classes are generated from
the same XML state definition files, they are always in synch.

The generated state machine classes follow the state design
pattern. Usually, we do not have to worry about the generated code.
To use the state machines, as illustrated in Figure 2, we need to
bind business objects, which are often implemented as plain old
Java objects (POJOs), and wire up the implementations of the action
and condition interfaces. Transitions, specified in the
OrderTransitions interface in our example, allow the
caller to trigger the state transitions of the state machine.

The state machine interfaces
Figure 2. State machine interfaces

Binding State Machine to Business Objects

All of the states and transitions are encapsulated in the generated
state machine class. That is, all of the state-transition-related
switch statements are centralized in the state machine class. As
shown in Figure 3, when the business service tier receives a
service request (say, shipOrder(orderId)), the request
will be delegated to the ship transition of the state
machine associated with the Order object. The
ship transition then triggers the doFill
action. One advantage is that we do not need to mix business logic
and state-related logic into the business objects (often POJOs), so
they can be reused in the presentation tier, as well as in the
persistence tier, by object-relational mapping (ORM) tools such as
Hibernate. More
importantly, the implementations of the transition actions, which
often do the real work for business service requests, are decoupled
from the state logic. This makes the action implementation cleaner,
reusable, and easy to test.

Business service request sequence diagram
Figure 3. Business service request sequence diagram

Note that the BusinessService is the only interface
visible to the presentation tier. It simply delegates the service
request to the state machine, and then the transition actions will
do the real work. The state machine factory creates and maintains
state machine wrapper objects, which also have references to the
underlying business objects, transition actions, and guard
conditions.

As mentioned earlier, business objects are persistent and
long-lived. They can be persisted and reloaded from a database
using a persistence mechanism such as ORM. But this is not the case
for state machines, even though they can be persisted by using
Java's object serialization. So the business object itself usually
has a flag field, such as an enum, to indicate its
state, and the state field must be persistent. Here is the
Order POJO:


package com.lizjason.smc.bo;

import java.io.Serializable;
import com.lizjason.smc.OrderState;

/**
 * The order placed by a customer.
 */
public class Order implements Serializable {
    private static final long serialVersionUID = 1;
    private String orderId;
    private OrderState state;
    private String userId;
    private String itemId;
    private String itemCount;

    public String getOrderId() {
        return orderId;
    }
    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }
    public OrderState getState() {
        return state;
    }
    public void setState(OrderState state) {
        this.state = state;
    }
    public String getItemCount() {
        return itemCount;
    }
    public void setItemCount(String itemCount) {
        this.itemCount = itemCount;
    }
    public String getItemId() {
        return itemId;
    }
    public void setItemId(String itemId) {
        this.itemId = itemId;
    }
    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
}

Now the question is how to bind a state machine to a business
object that is reloaded from a database. Remember, a Java state
machine class is just another way of representing the state diagram.
Like a state diagram, the object must enter and exit the state
machine via the start and end states (Created and
Shipped in our example), respectively. However, the
business object reloaded from the database may be already in a
different state; say, the Filled state for the
Order object. Thus, the state machine factory needs to
synchronize the current state of the state machine to the business
object state, which is not necessarily the start state. Because SMC-generated state machine classes are final, we cannot attach
additional behavior through subclassing, but we can create a state
machine wrapper to synchronize the current state with the business
object state. The following is the wrapper class,
OrderWOContextWrapper, for the state machine:


package com.lizjason.smc;

/**
 * The state machine wrapper. This class can be
 * generated using, for example, XDoclet, based
 * on the generated 
OrderWOContext. * * This main purpose of this class is to * synchronize the state machine internal state * to the state of the business object * */ public class OrderWOContextWrapper implements OrderTransitions { private OrderWOContext orderContext; public OrderWOContextWrapper(OrderWO owner){ orderContext = new OrderWOContext(owner); synchronizeState(owner); } private void synchronizeState(OrderWO owner){ OrderState state = owner.getOrder().getState(); switch (state) { case Created: orderContext.setState( OrderWOContext.MainMap.Created); break; case CheckingCredit: orderContext.setState( OrderWOContext.MainMap.CheckingCredit); break; case Filled: orderContext.setState( OrderWOContext.MainMap.Filled); break; case Shipped: orderContext.setState( OrderWOContext.MainMap.Shipped); break; default: throw new IllegalArgumentException( "Unsupported order state:"+state); } } public void view() { orderContext.view(); } public void submit(String userId, String itemId, int itemCount) { orderContext.submit(userId, itemId, itemCount); } public void fill() { orderContext.fill(); } public void ship() { orderContext.ship(); } public void reject() { orderContext.reject(); } }

Note that in the synchronizeState method, the
orderContext is the state machine. The
OrderWO is the owner of the state machine and it has
access to the underlying business object, as well as the
implementations of the actions and guard conditions. The
Order business object itself has no knowledge of the
state machine. The OrderWOContextWrapper, which
implements the OrderTransitions interface, simply
delegates the transition requests to the underlying state
machine.

An Architecture Using SMC

In the architecture illustrated in Figure 4, the business
objects are POJOs. Both the presentation and persistence tiers
reuse the business objects.

An architecture using SMC
Figure 4. An architecture using SMC

The presentation tier makes a service request through the BS
interface, which can be a proxy in the presentation tier.
Essentially, the BS is the service facade that encapsulates the
state machines and related components. The service request is first
delegated to the ServiceDispatcher, which is responsible for (with the help of the StateMachineFactory) identifying a state machine and binding the requested POJO business object to the state
machine. After that, a transition will be selected based on the
service request information and the state of the business object.
If the guard conditions are met, the transition will be executed,
which, in turn, will execute the associated actions. It is the
transition actions that implement the business logic.

Further, since BS is the entry to the business service tier,
optionally, we can register the service interceptors to handle
crosscutting concerns, such as logging, auditing, and security.

Conclusion

SMC is an open source tool that can generate state machine Java
classes from centralized state transition definitions. It allows the
decoupling of business object state transition logic from the
business logic implementation. It leads to simple, easy-to-test, and
reusable business logic implementations. It is a good tool for
simplifying business object state management and overall
implementation in business-object-centric applications.

Resources

width="1" height="1" border="0" alt=" " />
Related Topics >> Programming   |   

Comments

Thanks Jason. Very nice

Thanks Jason. Very nice article.