Business Object State Management Using State Machine Compiler Blog

Version 2



    Defining State Transitions
    Generating State Machine Classes
    Binding State Machine to Business Objects
    An Architecture Using SMC

    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.


    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 Ruby, C#, C++, Python, and 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 Orderobject

    The Order object has four states:Created, CheckingCredit,Filled, and Shipped. The transitions for the Order object include view,submit, reject, fill, andship. A transition may have guard conditions. For an example, isCreditBad is the guard condition for thereject 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: // %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 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 rejecttransition are CheckingCredit andCreated, 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 theOrder object in this example. Also, nilindicates 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 Orderobject 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 aship transition and the next state isShipped. If, say, the view transition applies to all of the states, we can simply add a viewtransition 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 theCheckingCredit 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 

    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 theOrderTransitions 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 shiptransition 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 asHibernate. 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 theOrder POJO:

    package; import; 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 (Createdand 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 theOrder 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, theorderContext is the state machine. TheOrderWO 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. TheOrder 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 theStateMachineFactory) 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.


    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.