UI logo
Agile Interaction Architecture
uidesign.net
 
     

David Anderson Headshot
Agile Management Cover Graphic
 
 

 
 
WhitePaper
Friday, June 02, 2000
 

A State Machine Engine for Web MVC

 


Introduction

This is Part 2 of a series of articles about modeling Web Presentation Layer implementation using Statecharts and then implementing that design using a generic server-side engine for processing incoming events and determining resultant states and HTML pages. If you haven't read Part 1, the justification and explanation of the logical architecture then you might like to do so first.

Handling Statecharts Generically

Assuming that you have modeled the implementation of your Presentation Layer Navigation using a Statechart, you could simply go on to develop a hard coded solution where one HTML page invokes another. You would simply hard code your events into your links and form submissions, your next states into the requested URLs. I have seen very adequate implementations of this particularly using Servlets. However, this is an inflexible approach which doesn't easily support condition evaluation and is likely to involve considerably more maintenance. It does not provide for a central facility to protect against unexpected use of bookmarking, back button or refresh and can lead to extensive code to protect the back end application from unexpected browser events.

Far better, if you have time and are willing to make the long term commitment, to invest in a genericised architecture which allows you to "soft" code the design of the system as a description in a database. This series of articles is about doing just that and now we are going to look at modeling an engine for the generic treatment and processing of events and state transitions.

Figure 1 presents an elementary Statechart containing all the notation elements with which we must cope in order to deliver the full functionality. We must model states which can have substates. There is an initial Start State and there is an End State. Sometimes a State can have a Start State as a child which must have an transition to an actual substate. Transitions from one state to another are made by Events. Sometimes an Event will contain arguments and those arguments may be evaluated with conditions. The resultant evaluation can determine the next state. Finally, events can carry Actions. The actions are conducted after the conditions are evaluated.

Figure 1 Basic Statechart Notation Elements which we need to model

In addition to the Start State there are also two other initial states shown inside State2. These are the History State and the Deep History State or History*. The History State means: enter State 2 and immediately enter the substate which was most recently occupied. Deep History State means: enter State 2 and immediately enter the most recently visited substate and immediately enter the most recently visited substate of it and keep going recursively until we have reached the deepest available level. In the example diagram, if we are in State 5 and EventX happens we will go to State 6. Some time later from State 8, we receive Event D which enters State 2 with History*. The actual resultant state will be State 5 which was the most recently visited child of State 2.

For User Interface applications, this return to the History of a previous state is incredibly powerful. If we open a Dialog Box, the Cancel action is almost certainly going to return us to the history state of the previous state which requested that Dialog, as shown in figure 2.

Figure 2 Opening a Dialog and returning to History* if Cancel is selected

It ought to be immediately apparent that this will also work for websites which are running a server side Statechart engine. You can, for example, navigate to a "Exit! Are you sure?" page which has a Yes or No option. If No is selected, simply return to the previous area of the application entering it with History*.

The use of this mechanism decouples the design. For example, State1 above could have new additional substates added such as State7 and State8. The Dialog would never need to know this. All it needs to know is that Cancel will return it to State1-History*.

The ability to model History and History* is a powerful addition and can be used to make websites behave more like GUI applications. Something which is in increasing demand as website users get more and more sophisticated.

Concurrency is missing

There is one aspect of the Statechart notation which is not modeled - Concurrency. Concurrency is the notion that the system can be in several independent states simultaneously. It was a simple design decision to avoid complexity. So far, for web site applications, the constraint that only single threaded Statecharts can be modeled has proven satisfactory.

Why should this be so?

Well we are only modeling the Presentation Layer state, not the underlying business system (Model Layer) state. In most business systems, there is an extreme amount of concurrency, however, in a web implementation of a User Interface, we are really delivering only a single html page. That single page does not need concurrency.

Naturally, there is the possibility to introduce concurrency as soon as you start to use frames. Whether or not there is concurrency in the UI is dependant on whether the frames can change independently or not. In most cases they do not change independently but in a synchronous fashion. Concurrency is an unnecessary addition and complexity which can safely be avoided for the time being.

Initial Model for Statecharts

In order to implement a generic engine for a State modeled, Event driven system, we need to be able to represent those States and Events as data at the server.

Figure 3 introduces an initial model shape for Statecharts. This model has a State class which knows its parent and any children. It also knows which of the children is the default Start State child. It was decided not to model the Start State or the End State explicitly. The action on the default Start State has been modeled with the method onEntryExecute() - [ see the Reflections section below] . The behaviour on the default Start State action will be executed by calling this method when a state is entered.

A State has a collection of Events which are permitted from that particular state. The Event in turn has a number of StateTransitions. The actual Transition taken will depend on the Condition Evaluations. This part of the model deals with the problems associated with EventD in Figure 1.

Figure 3. Class diagram to model an elementary Statechart.

Figure 3 does not model History or History* though some provision is made for it in the StateTransition which has an attribute entryCondition, this would have the enumerated values of [default; History; HistoryStar] but is shown as an int for brevity. [ The term HistoryStar is used from now on because Java syntax doesn't allow History* and UML tools can get confused. ]

A StateTransition showing HistoryStar would indicate that the resultant (or "to") State should be entered with HistoryStar. The current model however has no way of knowing what that might be. We are not storing any history of where the client has previously visited. This will require a more complex model.

On Entry Behaviour

It was found from the navigation designs that default start state actions are unusual. The most common usage is Start Transaction. For this reason, it was decided to break OO design purity and implement the onEntryExecute() method as a class method on the State class. The method contains a switch() statement which is switched on the stateID passed as a parameter. This decision was made in order to keep the Object Model clean and the persistence simple. This is broadly in keeping with the Coad approach to OO modeling. [ see the Reflections section later ]

For those who prefer OO purity, you would simply make the onEntryExecute() a stub method and over-ride it in a sub-class of State. You would provide one of these for each State which required default entry behaviour.

Modeling History

There are several possible ways to model the history of visited states. We could simply keep a Vector (or ordered collection) of States and iterate through it backwards looking for the most recently visited child of the new "to" state. This was, in fact, our initial approach. However, this is problematic as data is needed in order to rebuild that previously visited state. That data can change and is dependent on the User, the Login Session, perhaps other system activity. For example, say we are populating a table of bank account transactions using Account Number and Date. In order to accurately rebuild that screen at a later date, we need to know which Account Number and Date, the User had selected. At some point those values were entered as HTTP Parameters, so we had them. It is important to be able to retrieve them to rebuild the screen accurately.

The answer was the model shape in Figure 4. This model introduces the User's LoginSession and shows it holding a collection of visited states. This additional class can hold that specific data required to rebuild the view. In this case the HTTP Parameters which were current at the time the state was last visited.

Figure 4. Statechart model extended to support a history of state visits

In Figure 4, a LoginSession which is opened for each new client hitting the site for the first time, holds a collection (Hashtable) of most recent StateVisit objects. These represent the most recent time a State was visited by that LoginSession. This collection is sufficient to allow us to model History and HistoryStar which are only interested in the most recent visit to the state. A second ordered collection (Vector) is also being held. This is to allow future functionality such as "undo" to be introduced.

An <<interface>> is introduced which is used to hide a generic collection of Parameters. These are normally only the HTTP Parameters which accompanied the original request to goto the state. In the event, that some more complex attributes were needed such as specific business objects at the time the state was visited, the StateVisit class would need to be extended with a subclass to hold the reference to those additional and specific objects.

Figures 5, 6 and 7 show the Sequence Diagram for making a State Transition. This assumes that the engine has already determined which StateTransition object is appropriate and has determined the [default; History; HistoryStar] entry condition for the new State. The LoginSession class is responsible for making the transition. It will be called with the desired resultant state passed as a parameter. The sequence will require to make a StateVisit object and resolve issues such as default child or deep history before finally determining the ultimate resultant state.

For example, from Figure 1, a call to gotoState( State2 ) would normally result in State 3 becoming the resultant state visited. The gotoState() method must ensure that this happens by ensuring that th default child state is automatically entered on entry into State2.

Resolving the gotoState(), returnHistory() and returnHistoryStar() methods represents the resolution of the significant problems in modeling the Statechart. After this we can go on to look at the engine which processes HTTP Requests and drives the Event handling Controller code and the resultant View Classes.

Figure 5. Goto Next State, Sequence Diagram
[Click to see full size]

Figure 6. Return to History of a State, Sequence Diagram

Figure 7. Return to Deep History of a State, Sequence Diagram

Implementing the MVC Engine

So far we have seen how to model the Statechart. So now we can represent our Presentation Layer design as real Objects which we can store on the server side of the system. Now we will look at how to implement this with HTML clients and make the whole thing work together.

There are several problems. Firstly, our HTTP Request can only give us two Events - Get and Post. It is necessary for us to translate the Get or Post into the actual logical event from our diagram. In addition, we need to extract any incoming arguments (or parameters) from the HTTP request. Finally, we need to deal with the unexpected. We know that the client browser can change its State using the Back Button. We want to allow this - most of the time. We also know that we can receive unexpected Events because the client has jumped using a Bookmarked URL. We also want to allow this - at least sometimes.

Whether or not Back and Bookmark are allowed has a lot to do with Conversational State and Transactions. For the time being, we will continue to ignore this complexity so Back and Bookmark is allowed. This complex area is expanded in Part 3 of this article.

Connector

All web server environments have the concept of a connector. It's the code which is run when a request arrives, for example a Servlet or a JWeb Cartridge (with OAS v4.x). In this engine, the connector is purposefully lightweight. It is one of the few implementation dependant parts of the system. It would have to be re-written if you decided to move Web Server platform. Other platform specific parts of the engine are hidden behind Java interfaces. Making the engine and the Statechart design reasonably portable. The Connector is responsible for wrapping the platform specific HTTP Request behind the IHTTPRequest interface.

Request Manager

The Connector delegates to a RequestManager class which processes the incoming request. It must detect which Client is calling and get the associated LoginSession. It does this by extracting the SessionID parameter from the request. If there is no SessionID then the client is new so they begin at the StartState.

Assuming that we have a legal session, it must interpret the current client state, the incoming logical event, and use the State, Event and StateTransition descriptions to figure out what to do next. The client State and logical Event name should be encoded as parameters. It must invoke the appropriate event handling Controller class passing the HTTP Parameters which the controller may need to evaluate any Conditions. The Controller will also be responsible for executing any Actions which take place on the Event arrow. Eventually, the RequestManager must ensure that the appropriate View class is built and dispatched. This was delegated to the Controller classes in this design. The Request Manager also takes responsibility for error handling, acting as the ultimate bucket for catching exceptions. The reasoning for this is simple. RequestManager is well placed to invoke the appropriate recovery State after catching an exception or error.

Figure 8. The Engine to drive the Statechart

Interfaces for flexibility

You will see that a complex strategy of interfaces is employed for HTTP Parameters. The IHTTPParameters interface abstracts away the reality of the source of the parameters. For normal requests, we will pull the parameters from the incoming HTTP Request but when we need to enter History or HistoryStar then we have to recall parameters from persistent storage. Allowing the other parts of the engine to hold references to IHTTPParameters frees them from needing to worry where the parameters are coming from. They no longer need to care whether they came from an incoming request or from storage, the RequestManager can decide for them.

Delegation rather than Inheritance

Some OO purists may be uncomfortable with some of the design elements here. They may argue that there is little point separating out Connector and RequestManager. True! If you don't intend to move platform or you don't mind re-writing a slightly larger class if you do move, then by all means merge these classes on the design.

RequestManager also doesn't appear to do much but as you will see it becomes the ENFORCER for the transaction isolation and the judge with respect to the legal or illegal use of Back Button or Bookmarking from the client. RequestManager is the key class discussed in Part 3.

The second aspect which is not immediately obvious from figure 8 but will become apparent is that the Controller Class hiding behind the IController is actually containing behaviour which might otherwise be placed in the Event class - after all a Controller is an Event Handler. In fact, the Controller class also has behaviour which ought to be in the StateTransition class. The Controller has the condition evaluation logic - Event Class - and the action on transition logic - StateTransition Class. Implementing this delegated design with Event and IController separated is a key part of this implementation and its worth just spending a few moments to consider why it was appropriate.

The Event class represents all generic events on any Statechart. The Event class simply models the data needed for an Event such as the eventName, and any behaviour which relates that Event to States and StateTransitions. The Event class is generic in this respect and doesn't need to be subclassed or extended. The same is true for the StateTransition Class. In fact, what is being modeled here is data. The behaviour is separated out into the IController implementor. This lack of data and behaviour encapsulation is not pure object oriented design. OO is all about encapsulating data and behaviour. Why separate it out?

The implementors of IController represent application specific behaviour related to specific Event objects for a given application. It would be possible to extend the Event class and compose in the the Event Handling behaviour but this would be messy. It would give us the problem of maintaining lots of little sub-classes for Event. This makes our persistence messy and the whole engine becomes less portable. Its nicer to allow the Event class to hold the name of the IController implementing class and then allow the class loader to invoke the class on demand. After all, a Controller is not a special kind of Event.

A similar strategy was used for View classes. Views implement the IView interface. A View is responsible for building an HTML page. A specific view is tied to a specific State, so why not subclass (or extend) the State class, one for each View? Well again this would be messy and again it introduces inheritance simply for the sake of it. It messes up the persistence and overly complicates the State class which is purely data related. A View is not a special kind of State.

Separating out the application specific View and Controller behaviour away from the generic Statechart engine keeps the design flexible, scalable and portable. Its easy to develop a tool which will automatically populate the State and Event Objects for a given Application Presentation Layer design. This can be done is isolation away from the code development for the Views and Controllers. Figure 9 shows this in more detail.

[ Brian O'Bryne of ebeon.com has demonstrated that Together/J can be used to output data into an XML format which can be used to automatically populate a persistent datastore for States, Events and StateTransistions]

Figure 9. State and Event delegate to IController and IView to get application specific work done.

Controllers

Its worth taking a little time to consider what is going on behind the IController interface. You ought to find that a great deal of any website design contains simple Navigation. Take the client from one location to another. It was found that this can be true for around 75% of all Event arrows in a typical eCommerce application. It makes sense therefore to write a single generic class which can handle these Events. This is the OneToOneController. It is there to be used most of the time to simply select the Next State based on the single StateTransition held by the Event. Not shown on the diagram, it is necessary to extend OneToOneController, if there is an Action to be performed such as commit() on a transaction.

The OneToManyController is an abstract class which is designed to be extended for all the other Events which require conditions to be evaluated. The OneToManyController class contains code to execute the transition which is re-used by the sub-classes. Not shown, is the protected abstract method evaluateConditions() which must be over-ridden by the sub-classes. This method should return the selected StateTransition to be used. Its important to realise that this is again a deviation from "pure" OO principles. The evaluateConditions() method really belongs across the StateTransition objects as an evaluateCondition() method, however, it is being separated into the Controller class to keep the main engine separate from specific application behaviour. There are definite performance advantages in a design which evaluates all the conditions together rather than as a set of rules separate out into a collection of StateTransition objects. Consequently, StateTransition really ought to have an doAction() method which is by default a stub method which does nothing. Again, this behaviour is in the Controller class and is the code which is executed on successful evaluation of a single condition.

Views

Views are considerably simpler. They implement the contract in the IView interface that is all. There is no concept of super or abstract classes. In the current implementation a View class builds an HTML page from scratch using basic HTML Tag classes provided by the platform being used.

An area for future research is the creation of a richer set of re-usable UI classes which could be composed into the View classes in the style of a toolkit such as JFC. Such a scheme is now easily possible with the use of JSPs which can call other JSPs thus constructing a single HTML page from a collection of classes. Other possibilities include the use of HTMLKona from Weblogic.

Puting it all together

That just about covers all the basic functionality required for a generic engine. To make it a little more flexible, the final model adds the notion of an Application. An Application contains a number of States. This allows the one server to run several different applications simultaneously. This can be advantageous if they need to share the same business logic, or you can only run one version of your server environment but need to serve up multiple applications / web sites. A typical use for this might be Client User Application and a separate Technical Support Application used for maintenance and telephone support. The Technical Supprt application typically supporting features such as replacing a lost User ID and password.

The Application is also the Class which knows the Start State. It holds it as a special reference. You will recall that we didn't explicitly model the Start State. The End States are naturally those with no Event transitions and no children - in effect Dead Ends. Detecting an End State is not difficult. The result of arriving at an end state is to terminate the LoginSession by setting the end time.

The Final addition to the completed model in Figure 10, is the UnitOfWork Class which is there to support the concept of a Logical Transaction Unit of Work. Supporting transactions adds more complexity to the Statechart engine but it is the highly controlled and elegant solution that Statechart modeling offers for Transaction Scoping and Isolation which makes the implementation of a Statechart engine so attractive.

Figure 10. The complete Statechart based MVC Engine model
[Click to see full size]

Reflections

During development of this paper, it has become apparent that several further improvements could be made. The onEntryExecute() method is an ugly kludge. Better might be to place it into the View class. Afterall the View class represents the State. View classes also have methods for validating inputs. However, not all States have View classes. So implementing the onEntryExecute() in the View class was rejected.

Brían O'Bryne has suggested changing the model to allow the notion of a default Event for a State. The default Event could in turn have a Controller which implements any "onEntry" action required and does a gotoState() for the appropriate default child State. This is a considerable improvement on the current design. The default Event would be optional i.e. 0..1, and would only exist when a transition to a default child state was required.

Finally, in the original design, LoginSession did not directly couple to the Statechart Engine, instead it delegated through a class called Context. Context was the non-persistent delegate for LoginSession. LoginSession interacted with all the Business Layer classes which were application specific. The Context class was a generic delegate which was part of the Statechart engine. As has been argued elsewhere, there is considerable merit in this approach. The Statechart engine becomes truly generic and portable. Portability promotes re-use. With infrastructure code such as this, re-use is vital. Its just too risky to build this kind of thing for every application. Its like an operating system - dangerous to play around with.

Part 3 - Transaction and Exception Support

Part 3. Transaction Support for Conversational transactions is such an important topic which requires in-depth analysis that I have decided to give it a separate article . Part 3 will also look at Exception Handling and why a Statechart Engine implementation makes exception recovery conceptually simple and deterministic.

Reference Material

Constructing the User Interface with Statecharts, Ian Horrocks, Addison Wesley, 1998

Java Design: Building better apps and applets, 2nd ed., Peter Coad and Mark Mayfield, with Jon Kern, Prentice Hall, 1999

Java Modeling in Color with UML, Peter Coad, Jeff De Luca and Eric Lefebvre, Prentice Hall, 1999

A similar architecture was discussed in brief at Servlet Central in late 1998, www.servletcentral.com

Acknowledgments

This paper was written following earlier work in the area conducted atTrinity Commerce , Dublin, Ireland. The design presented here represents version 2 of such an architecture and incorporates several changes and enhancements which we noticed along the way. This version is a pure Java solution. Version 3 of this framework is now commercially available as ViewControl from Statesoft. The original design was an Object-Relational solution and was built on Oracle Application Server v.4.x . I owe thanks to Marcus O'Connell and Brían (pronounced breen) O'Byrne who helped with the early models for this system. Marcus went on to built the first prototype and later Martin Byrne, Mark McSherry and I developed it into a full production system. Brian Murray, with Jenny Hay and Philip Miles (both Oracle Consultants) all contributed to the implementation of the persistent storage and the data entry forms for States, Events, Applications and Units Of Work.

Thanks to Brían O'Byrne, Will Estes, Jim Avery and Ian Horrocks for review comments and assistance with the diagrams.

Notes

This article contains extensive UML diagrams for the problem solution including Sequence Diagrams. The nature of these is that they are large when displayed in GIF format for publishing. As such you may experience difficulties trying to print them. It is really impossible to adequately publish these diagrams in a standard html page. To fit the page they must be small but to be readable they must be large. It was decided to stick with large, readable diagrams rather than printable diagrams.

Advertisement

For more information about the commercial Web MVC framework ViewControl visit Statesoft.

     
 
           
hosted by likk.net
Weblog Commenting by HaloScan.com