REST and FSM and BP for Quixote How-to

Dave Kuhlman

http://www.rexx.com/~dkuhlman
Email:

Release 1.01
Feb 25, 2003

 
Front Matter

Copyright (c) 2003 Dave Kuhlman

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Abstract:

This document describes the use of finite state machines (FSM) to implement complex processes in a REST style. Example implementations built on top of the Quixote Web application server are provided.



Contents

1 Introduction

Business processes (BPs) and other complex processes are primarily procedural and secondarily give access to resources. REST is primarily about access to resources and secondarily implements processes.

Since BPs are procedural, it is not obvious how to fit them into the REST style. This documents attempts to describe a way to map BPs onto Web applications in the REST style.

Our strategy is as follows -- Let us assume that BPs map fairly naturally onto finite state machines (FSMs) and onto the digraphs that describe FSMs. This document explains and gives examples of implementing FSMs in a REST-ful style. That is, we give several programming patterns for implementing FSMs in a REST-ful style using Quixote.

2 REST and Business Processes

This section is here to recognize the need to translate BPs into FSMs. I don't have a solution to that problem (although in the future, I hope to develop tools that help here).

A criticism of a possible alternative strategy -- Suppose we have a BP described in some sort of pseudo code. We might ask: Why not implement the pseudo code more directly without first translating the BP or pseudo code into and FSM? This suggestion makes two related assumptions: (1) The process can be implemented in a style that is similar to that of an application running on a single machine. (2) The process can be implemented in a style as though it will run on a LAN. But, the Net is not a LAN. And, if you proceed down the path of implementing pseudo code directly, you will find yourself following a RPC like strategy. However, "REST is incompatible with 'end-point' RPC." (See The REST FAQ, 6 Are REST and RPCs incompatible?.)

I believe the above criticism is applicable to any design strategy that is intended for developing applications for a single system, whether that design strategy is object-oriented or procedural. In particular, this also applies to UML.

This document presents a strategy for implementing BPs and other complex processes in a REST-ful style that avoids pitfalls of XML-RPC and state on the server that are likely to result from a pseudo code or UML design strategy.

2.1 Why FSM

Given our interest in exposing BPs (business processes) across the Web in a REST-ful style, we might ask, Why would we want to translate BPs into FSMs? This section attempts to answer the question: Do FSMs work well with BPs and REST.

Some of the reasons have to do with the design process:

And, some of reasons have to do with implementation and with REST:

3 FSMs and RTNs

Although we assume a good deal of knowledge about finite state machines, this section attempts to provide enough information to put us all ``on the same page''.

One cycle of the ``execution'' of the FSM starting at any given current state can be described as follows:

  1. Check the conditions on each of the transitions from the state (i.e. the edges that lead from the node). Select a transition.

  2. Execute the action associated with the transition.

  3. Change the current state to the state (node) that is the destination of the selected transition.

  4. Repeat for the new current state.

In our examples we will be attempting to implement the execution model described above in a REST-ful way. Here is rough description of our approach:

4 Implementation

We will describe several ``programming patterns'' for the implementation of FSMs on top of Quixote in a REST-ful style.

These patterns have the following principles in common:

We will describe several application structures or patterns. Here is a table that lists them:

          State     FSM/Container 
1. method class
2. function module
3. class module
4. module package

And here is a brief description of each of these patterns:

  1. Method/class -- Each state is implemented by a method. The methods are contained in a single class. The class implements the FSM. The name of each method is the same as the name of the state that it implements.

  2. Function/module -- Each state is implemented by a function. The functions are contained in a single module. The module implements the FSM. The name of each function is the same as the name of the state that it implements. (An alternative would be to put separate functions in separate modules.)

  3. Class/module -- Each state is implemented by a separate class. The classes are contained in a single module. The module implements the FSM. The name of each class is the same as the name of the state that it implements. (An alternative would be to put separate classes in separate modules.)

  4. Module/package -- Each state is implemented by a separate module. The modules are contained in a single Python package. The package implements the FSM. Within the modules, the state may be implemented by either (1) a function or (2) a class. The name of each module is the same as (or is related to) the name of the state that it implements.

And, here are some additional ways in which each of the above patterns can be modified.

And, here are a few implementation recommendations or guidelines:

5 Examples

This section provides examples of several patterns for the implementation of FSMs in a REST-ful style. The source code for these examples is available at fsm_examples.zip.

Notes on running the examples -- The examples do a database lookup. I use PostgreSQL and access it through the Ns interface exposed by PyWX. You can remove this dependence by replacing the function getDescription in each example with a function that takes a name as an argument and returns a (descriptive) string. Here is an example (which is also included at the bottom of example1.py):

PlantDict = {
    'blackberry': 'good in cobbler',
    'brocolli': 'rich in vitamins',
    'butternut': 'good squash',
    'coriander': 'good in pickles',
    'grapefruit': 'pink and tart',
    'greenpepper': 'nutricious',
    'kabocha': 'rich orange color',
    'kumquat': 'cute little citrus',
    'mustard': 'tangy yellow',
    'nectarine': 'summer treat',
    'orange': 'nutricious citrus',
    'parsley': 'plain and Italian',
    'peach': 'suculent',
    'plum': 'purple and tangy',
    'prune': 'sweet dried fruit',
    'rosemary': 'fragrant herb',
    'tangerine': 'easy to peel',
    'taragon': 'aromatic',
    'thistleseed': 'needs special feeder',
    'tomato': 'yummy and red',
    'walnut': 'oily nut',
}

def getDescription(name):
    if PlantDict.has_key(name):
        return PlantDict[name]
    else:
        return ''

5.1 Example preliminaries

5.1.1 Exposure

Since all the examples are in a single directory, we expose them all to Quixote through a single file __init__.py in that directory. The following statement exposes our applications to Quixote.

_q_exports = ['example1', 'example2', 'example3', 'example4', 'example5', 'example6']

Given the above, requesting a BP whose name is, e.g. ``bp1'' from a host named ``host1'' might use a URI such as ``http://host1/BP/example1''.

5.1.2 Dispatching

Each example application contains an dispatcher function. The dispatcher maps the current state name to the function or class that implements that state, then calls that function or creates an instance of the class and calls a method in the class.

In the examples implemented in a single module, the dispatcher is a _q_index or _q_getname function in the module. In examples implemented in multiple modules within a single package (or directory), the dispatcher is in the __init__.py file in the directory.

If the current state name is passed to the server as the last (right-most) component of the URI, then use _q_getname to implement the dispatcher. Here is an example from example2:

def _q_getname(request, name):
    buf = request.stdin
    content = buf.read()
    doc = statesub.parseString(content)
    state = name
    arg1 = doc.getFsminput().getArg1()
    cls = getStateDict().get(state, None)
    if cls:
        obj = cls(doc)
        return obj._q_index(request)
    else:
        raise TraversalError('No such state: %s' % state)

Comments:

And, here is an example of a dispatcher used when the current state name is passed to the server as an element in the XML document passed between client and server.

def _q_index(request):
    buf = request.stdin
    content = buf.read()
    doc = statesub.parseString(content)
    state = doc.getFsmcurrentstate()
    arg1 = doc.getFsminput().getArg1()
    cls = getStateDict().get(state, None)
    if cls:
        obj = cls(doc)
        return obj._q_index(request)
    else:
        raise TraversalError('No such state: %s' % state)

Here is an example of the dictionary used to map state names to state implementations in our simple examples:

StateDict = {
    'Start': Start,
    'Plant': Plant,
    'Fruit': Fruit,
    'Vegetable': Vegetable,
    'Continue': Continue,
    'Finish': Finish,
    'Error': Error
    }

5.1.3 Overview of the examples

Each of the examples implements pretty much the same BP and FSM. And, since I refuse to attempt to create ASCII art for the digraph, here is a brief description:

The main exception to the above scenario is example6, which is our example of an RTN (recursive transition network), and shows how one FSM can ``call'' another.

Here is a summary of the examples:

5.2 example1

Here is the dispatch function:

def _q_index(request):
    buf = request.stdin
    content = buf.read()
    doc = statesub.parseString(content)
    state = doc.getFsmcurrentstate()
    arg1 = doc.getFsminput().getArg1()
    cls = getStateDict().get(state, None)
    if cls:
        obj = cls(doc)
        return obj._q_index(request)
    else:
        raise TraversalError('No such state: %s' % state)

Comments:

Here is an example of a class that implements a state, in this case the Plant state:

class Plant(State):
    def __init__(self, doc):
        State.__init__(self, doc)
    def _q_index(self, request):
        doc = self.doc
        arg1 = doc.getFsminput().getArg1()
        arg2 = doc.getFsminput().getArg2()
        if arg1 == 'fruit':
            nextState = 'Fruit'
        elif arg1 == 'vegetable':
            nextState = 'Vegetable'
        else:
            nextState = 'Error'
        self.setXmlResponse(request)
        return """\
<fsmstate>
    <fsmcurrentstate>%s</fsmcurrentstate>
    <fsmoutput>
        <content>This is response for input "%s".</content>
    </fsmoutput>
    <fsminput>
        <arg1>[plant name]</arg1>
        <arg2></arg2>
    </fsminput>
</fsmstate>
""" % (nextState, arg1)

Comments:

5.3 example2 -- example4

These are similar enough to example1. So, I'll let you figure them out by looking at the source.

5.4 example5

The difference in this example is that there is a separate module for each state and all the modules are collected in a single Python package. This is over-kill for our simple example, but does suggest how this design could scale to the needs of larger projects.

Here is the dispatcher:

# __init__.py

import Ns
import statesub

# Import the state implementations.
from start import Start
from plant import Plant
from fruit import Fruit
from vegetable import Vegetable
from continuemod import Continue
from finish import Finish
from error import Error

_q_exports = []

# A dictionary that maps state names to state implementations.
StateDict = {
    'Start': Start,
    'Plant': Plant,
    'Fruit': Fruit,
    'Vegetable': Vegetable,
    'Continue': Continue,
    'Finish': Finish,
    'Error': Error
    }

def getStateDict():
    return StateDict

#
# Dispatch to the state implementation.
#
def _q_index(request):
    buf = request.stdin
    content = buf.read()
    doc = statesub.parseString(content)
    state = doc.getFsmcurrentstate()
    arg1 = doc.getFsminput().getArg1()
    cls = getStateDict().get(state, None)
    if cls:
        obj = cls(doc)
        return obj._q_index(request)
    else:
        raise TraversalError('No such state: %s' % state)

Comments:

The individual states are implemented in a class in a module. Here is an example of a state implementation:

# vegetable.py

from state import State

class Vegetable(State):
    def __init__(self, doc):
        State.__init__(self, doc)
    def _q_index(self, request):
        doc = self.doc
        arg1 = doc.getFsminput().getArg1()
        name = arg1
        descrip = self.getDescription(name)
        self.setXmlResponse(request)
        return """\
<fsmstate>
    <fsmcurrentstate>Continue</fsmcurrentstate>
    <fsmoutput>
        <content>The description for vegetable "%s" is "%s".</content>
    </fsmoutput>
    <fsminput>
        <arg1>['finish' or 'restart']</arg1>
        <arg2></arg2>
    </fsminput>
</fsmstate>
""" % (name, descrip)

Comments:

Now, here is the state module, which contains several methods used by multiple state implementations.

# state.py

import Ns

class State:
    def __init__(self, doc):
        self.doc = doc
    def setXmlResponse(self, request):
        response = request.response
        response.set_header('Content-type', 'text/xml; charset=iso-8859-1')
    def getDescription(self, name):
        conn = Ns.GetConn()
        server = conn.Server()
        pool = Ns.DbPoolDefault(server)
        dbHandle = Ns.DbHandle(pool)
        rowSet = dbHandle.Select("select * from plants where name = '%s'" % \
            name)
        if dbHandle.GetRow(rowSet) == Ns.OK:
            values = rowSet.values()
            descrip = values[1]
        else:
            descrip = 'Plant "%s" not in database.' % name
        return descrip

Comments:

5.5 example6 -- A sub-network and RTN

This example shows how to ``call'' one network from another. It is composed of two FSMs:

The structure and implementation of both example6 and example6a are similar to example1. In particular, each state is implemented as a class and all classes (states) are in a single module. Furthermore, the same URI is used for each request and the current state is carried in and extracted from the XML document used to communicate between the client and server.

How to ``call'' a sub-network:

We'll look at some of the relevant code.

Here is the Plant state, which calls the sub-network:

#
# Transition to the Fruit or the Vegetable state depending on whether the
#   input (arg1) is 'fruit' or 'vegetable'.
# First call the plant list subnetwork to produce a list of fruits or vegetables.
#
class Plant(State):
    def __init__(self, doc):
        State.__init__(self, doc)
    def _q_index(self, request):
        arg1 = self.doc.getFsminput().getArg1()
        arg2 = self.doc.getFsminput().getArg2()
        if arg1 == 'fruit':
            nextState = 'Fruit'
        elif arg1 == 'vegetable':
            nextState = 'Vegetable'
        else:
            nextState = 'Error'
        if nextState in ('Fruit', 'Vegetable'):
            stack = self.doc.getFsmstack()
            stack += ' example6.%s' % nextState
            self.doc.setFsmstack(stack)
            self.doc.setFsmurl('/FSM/example6a/')
            self.doc.setFsmcurrentstate('Start')
            self.doc.getFsminput().setArg1("['fruit' or 'vegetable']")
        else:
            self.doc.setFsmcurrentstate(nextState)
        content = self.generateContent()
        self.setXmlResponse(request)
        return content

Comments:

Next, here is the implementation of the Finish state from the sub-network (example6a), where the return to the calling network is implemented.

#
# If there is something on the return stack, pop off the top (right-most)
#   item and return to it.
# If the return stack is empty, then just clean up and finish up.
#
class Finish(State):
    def __init__(self, doc):
        State.__init__(self, doc)
    def _q_index(self, request):
        self.setXmlResponse(request)
        stack = self.doc.getFsmstack()
        stackItems = stack.split()
        if len(stackItems) > 0:
            item = stackItems.pop()
            bp, nextState = item.split('.')
            self.doc.setFsmurl('/FSM/%s/' % bp)
            self.doc.setFsmcurrentstate(nextState)
            stack = string.join(stackItems, ' ')
            self.doc.setFsmstack(stack)
            self.doc.getFsmoutput().setContent('Enter %s name.' % nextState.lower())
            self.doc.getFsminput().setArg1("Enter plant name.")
        else:
            self.doc.getFsminput().setContent('Thank you for using example6a.')
        self.doc.getFsmoutput().setContent('')
        self.doc.getFsminput().setArg2("(example6a.Finish)")
        content = self.generateContent()
        self.setXmlResponse(request)
        return content

Comments:

6 Evaluations, Conclusions, Etc

6.1 Is it REST-ful?

A few comments:

6.2 Does it solve the problem?

I'm arguing that we have solutions to the following problems:

 
End Matter

Acknowledgements and Thanks

Thanks to the implementors of Quixote for producing an exceptionally usable application server that is so well suited for REST.

See Also:

The RESTWiki
for more information and links on REST

The main AOLserver Web site
for more information on AOLserver

The PyWX Web site
for more information on PyWX

Quixote home page
for more information on the Quixote Python Web application framework

The PostgreSQL Web site
for information on PostgreSQL

Beginner's How-to for AOLserver, PyWX, and PostgreSQL
for more information on using AOLserver, PyWX, PostgreSQL and Quixote

REST for AOLserver, PyWX, and Quixote
for more information on implementing REST-ful applications with Quixote

generateDS.py -- Generate Python data structures from XML Schema
for more information on generateDS.py

About this document ...

REST and FSM and BP for Quixote How-to, Feb 25, 2003, Release 1.01

This document was generated using the LaTeX2HTML translator.

LaTeX2HTML is Copyright © 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of Leeds, and Copyright © 1997, 1998, Ross Moore, Mathematics Department, Macquarie University, Sydney.

The application of LaTeX2HTML to the Python documentation has been heavily tailored by Fred L. Drake, Jr. Original navigation icons were contributed by Christopher Petrilli.