|
|
|
REST and FSM and BP for Quixote How-to |
|
|
REST and FSM and BP for Quixote How-to
Dave Kuhlman
http://www.rexx.com/~dkuhlman
Email: [email protected]
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.
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.
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.
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:
- There is a good conceptual fit between BPs and FSMs. In
particular (1) A larger, complex process can be understood as a set
of simpler, discrete steps (states of an FSM). And, (2) each step
in the process can be understood as (a) a set of choices based on
inputs, (b) an action determined by the choice, and (c) a transfer
to a new step (state) in the process.
- Because an FSM breaks a complex process into simpler,
discrete steps, FSMs are especially good for large complex
processes.
- While creating the design, for each step (state) in the
process, the designer can ask: What should the process do next?
And, what should it do, given this input, at this point in the
process?
- While attempting to check and validate the design, the
designer can ask: Does the design do the correct thing at this
point in the process? And, we just did X, we've got input M, and
the design says to do Y (and not, e.g., Z). Is that the correct
thing to do?
- The designer can ask those who know the BP (for example,
those who work in and know the enterprise): Suppose we've just done
X and we're about to do Y. Is that the right thing to do? That
is, does what the design say to do agree with what those with
experience with the process say should be done.
And, some of reasons have to do with implementation and with REST:
- The FSM (i.e. the server-side application) does not have to
carry state. It does not have to track sessions. In a REST-ful
design, keeping track of state information is a client
responsibility. Eliminating the need to keep track of sessions and
state information simplifies the implementation.
- An FSM design simplifies what the client needs to understand
about a complex process. This design style limits the amount that
the client needs to know at any point in the process. The client
needs to know, at a given step in the process, what information to
submit and what URI to submit it to. A possible client's point
of view is: At this point in the process I need to submit these
information items to this URI.
- The client does not need to be concerned with the logic of
the BP/FSM. The client need only submit the correct information,
and the FSM will do the correct thing. The FSM will pick (and
execute) the correct action and will select the right transition to
the correct next state.
- The design we have chosen is a good fit for REST. It uses a
single URI for each process (i.e. for each BP/FSM). This fits
REST's guidance to name every resource.
- The client behavior can be very well-defined and clearly
explained. For each step/state tell the client: Fill in and submit
this XML document to this URL.
- We can provide the client developer with tools to build the
client app, for example, (1) a parser for each XML doc type; (2) a
graphical user interface skeleton, specifically a .wxg file
for wxGlade.
- An FSM is not pseudo-code. Pseudo-code and logic etc
is what is wrong with SOAP, XML-RPC, and other RPC-style Web
services. REST avoids that kind of "programmable Web" thinking,
and we do not want to let it sneak back in.
- REST says: "Here is a resource you can request." REST does
not say: "Here is logic (pseudo-code) that you can
implement." Implementing BPs as FSMs in a REST-ful style follows
that advice.
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''.
- An FSM describes a process. The description can be given by a
directed graph or digraph, that is a graph composed of (1) a set of
nodes and (2) a set directed edges that connect the nodes.
- Each node has a name and is a state in the FSM.
- The edges describe transitions from one node or state to
another.
- Since more than one edge can lead from the same node, each
edge may include a selection criteria or condition which is used to
determine which transition to use.
- There is an action associated with each transition. When the
transition is selected, the action for the selected transition is
performed.
- Each FSM has several special states (or nodes), in particular
there is always a start state and one or more finish states.
One cycle of the ``execution'' of the FSM starting at any given
current state can be described as follows:
- Check the conditions on each of the transitions from the state
(i.e. the edges that lead from the node). Select a transition.
- Execute the action associated with the transition.
- Change the current state to the state (node) that is the
destination of the selected transition.
- 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:
- Requests -- Each request is a request that the application
perform one cycle described above. In particular, the application
will respond to a request by:
- Selecting a transition from the current state -- The current
state is submitted by the client with the request. The selection
is possibly based on input submitted with the request.
- Executing the action associated with the selected transition
-- The action may have side-effects on the server-side. The
action may also produce output to be returned to the client; in
REST-speak, we call this output the representation of the resource.
- Response -- The output from the action (the representation of
the resource) and the name of new current state are returned to the
client.
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:
- The current state is carried on the client. In particular,
the name of the current state included in the XML document returned
to the client.
- Each request from the client is a request that the server (1)
select a transition (to a new current state), (2) perform the
action associated with that transition, (3) generate some content
(i.e. the representation of the resource), and (4) return to the
client an XML document containing the new current state name and
the representation of the resource.
- The implementation includes a dispatcher -- The dispatcher
obtains the current state name submitted by the client and calls
the implementation of that state.
- The name of the state and the name of the implementation of
the state are the same. We could also have chosen a convention
where the name of the state and the name of the implementation are
different but related, for example ``State22'' and
``State22_impl''; however, for our examples, at least, there seems to
be no good reason to do so.
- Every request returns a resource, in our case an XML document
that represents the resource. The resource contains the information
needed to make the next request. Specifically, the XML document
returned contains either (1) a URI that contains the name of the
(next) current state or (2) a URI that remains constant plus the
name of the (next) current state. The XML document type and its
structure is application (BP) dependent, however, it is likely that
all these FSM server-client interchange documents would have a
common area that contains things like (1) the URI for the next
request, (2) the (next) current state name, and (3) additional state
information.
We will describe several application structures or patterns. Here
is a table that lists them:
1. |
method |
class |
2. |
function |
module |
3. |
class |
module |
4. |
module |
package |
And here is a brief description of each of these patterns:
- 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.
- 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.)
- 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.)
- 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.
- The current state can be submitted to the server either (1) as
the last (right-most) component of the URI or (2) embedded in the
XML document submitted with the request. Submitting the current
state as part of the URI can be especially needed when implementing
an application that does not submit XML content with the request.
And, here are a few implementation recommendations or guidelines:
- Provide a unique URI for each BP, i.e. for each FSM that
implements a BP.
- Separate (1) the implementation of the test that selects a
transition and (2) the implementation of the action for the
transition into two separate methods or functions. This
refactoring may enable you to reuse one or the other of these.
- Consider implementing a separate transition class or object.
A transition could, for example, be able to (1) test whether it is
the transition to be selected, (2) perform an action and return
output (the representation of the resource), and (3) return the
name of the state which is its destination.
- Use POST to make your requests. Why? Each
transition includes an action, and the action may cause
side-effects on the back-end. Guide-lines say, if the request can
have side-effects, do not use GET.
- Pass input to the application from the client to the server in
an XML document in the body of the request. Pass output from the
server to the client in an XML document. Use
Content-type ``text/xml'' for both.
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 ''
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''.
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:
- Note that the state name is received as an argument to
_q_getname.
- The example assumes that there is a dictionary, returned by
getStateDict that maps state names to the objects
(functions or classes) that implement them.
- Modules statesub and statesup (which is
imported by statesub) contains a parser and data
representation classes for the XML document that is passed back and
forth between the server and client.
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)
- Note that the state name (state) is extracted from
the XML document.
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
}
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:
- From the Start state, proceed unconditionally to
the Plant state.
- From the Plant state, proceed to either the Fruit state or the
Vegetable state depending on whether input is ``fruit'' or
``vegetable''.
- From the Fruit state, proceed to the Continue state. The
input is a fruit name. The output is a description of the fruit.
- From the Vegetable state, proceed to the Continue state. The
input is a vegetable name. The output is a description of the
vegetable.
- From the Continue state proceed to either the Plant state or
the Finish state, depending on whether the input is
``restart'' or ``finish''.
- From the Finish state proceed nowhere. Output is a
sign-off message.
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:
- example1:
- Structure -- A module implements the FSM. A separate class
implements each state.
- URI -- The same URI is used for each request. The current
state is passed back and forth in the XML content.
- Dispatching -- _q_index function parses the XML content (from
the client), extracts the current state, and dispatches by creating
an instance of the class for that state and calling the _q_index
method in that class.
- example2:
- Structure -- A module implements the FSM. A separate class
implements each state.
- URI -- The last component of the URI is the name of the State
which is the name of the class that implements the state.
- Dispatching -- _q_getname function uses the last (right-most)
component of the URI to determine the current state. _q_getname
parses the XML content (from the client) and dispatches by creating
an instance of the class for that state and calling the _q_index
method in that class.
- example3:
- Structure -- A module implements the FSM. A separate function
implements each state.
- URI -- The same URI is used for each request. The current state is
passed back and forth in the XML content.
- Dispatching -- _q_index function parses the XML content (from the
client), extracts the current state, and dispatches by calling
the function that implements that state.
- example4:
- Structure -- A module implements the FSM. A separate
function implements each state.
- URI -- The last component of the URI is the name of the State
which is the name of the class that implements the state.
- Dispatching -- _q_getname function uses the last (right-most)
component of the URI to determine the current state.
_q_getname parses the XML content (from the client) and
dispatches by creating an instance of the class for that state
and calling the _q_index method in that class.
- example5:
- Structure -- A Python package implements the FSM. A separate
module (in that package) implements each state.
- URI -- The same URI is used for each request. The current
state is passed back and forth in the XML content.
- Dispatching -- _q_index function parses the XML content (from
the client), extracts the current state, and dispatches by creating
an instance of a class in the module that implements the state and
calling the _q_index method in the class in that module.
- example6:
- Structure -- This example shows one FSM ``calling'' another.
It somewhat based on recursive transition networks (RTN). Each of
the two FSMs in this example have the same structure as
example1, specifically: a module implements each FSM; a
separate class implements each state.
- URI -- The same URI is used for each request. The current
state is passed back and forth in the XML content. There is also
an element in the XML content that contains a stack of
networks/state pairs to return to.
- Dispatching -- _q_index function parses the XML content (from
the client), extracts the current state, and dispatches by creating
an instance of the class for that state and calling the _q_index
method in that class.
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:
- The current state is extracted from the XML document submitted
by the client with the request.
- The dispatcher creates an instance of the class that
implements the current state and calls the _q_index method
in it.
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:
- Method _q_index implements the behavior of the state. In
particular, it (1) determines the next current state; (2) performs any
action; (3) produces content to be returned to the client.
- Arguments (arg1 and arg2) are extracted from the
XML document submitted by the client.
- In the implementation of this particular state, arg1 is used
to determine the next current state.
- The content (representation of the resource) returned to the
client is an XML document that includes the name of the (next)
current state and instructions on arguments to be submitted with
the next request (``[plant name]'').
These are similar enough to example1. So, I'll let you
figure them out by looking at the source.
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 states are implemented in a class in each ``state''
module. So, we
import
each state/class.
- A possible efficiency improvement might delay importing the
modules containing state implementations until we determine which
state is needed, then only import the module implementing that
(current) state.
- We use a state dictionary to map state names to state
implementations.
- The function _q_index extracts the current state
name from the XML interchange document, maps the state name to a
class that implements it, creates an instance of that class, and
calls the _q_index function in that class.
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:
- The constructor method (__init__) saves the XML
document. Note that the document has already been parsed to create
Python objects.
- The _q_index method extracts the name of a plant from
the document, then calls the getDescription method,
implemented in the super-class State, to obtain a
description of the plant.
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:
- The database is accessed through the Ns module.
PyWX provides wrappers for these capabilities, which are
implemented by AOLserver.
This example shows how to ``call'' one network from another. It is
composed of two FSMs:
- example6 looks up a plant in the database and, given
the name of the plant, returns a description of the plant. This
BP/FSM calls example6a so that the client can obtain a list
of fruits or a list of vegetables.
- example6a provides a list of either (1) fruits or (2)
vegetables.
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:
- I've added one piece of information to the XML interchange
document, specifically an XML element
"<fsmstack>...</fsmstack>". It contains a string of the form
"fsm1.state1 fsm2.state2 ...". The right-most element of this
string is the FSM and state to be returned to. (Basically, it
serves as a return address.)
- The calling network (example6) and state pushes an
FSM/state pair onto the right end of this string. It then sets the
(next) current state to the Start state of the FSM to be
called and the URI to the URI of the target FSM. In particular, it
sets the "<fsmcurrentstate>" and "<fsmurl>" elements of
the XML interchange document.
- The Finish state of the called network
(example6a) pops the right-most element off this string.
It then returns to that FSM and state.
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:
- The _q_index first gets the stack string (using
"self.doc.getFsmstack()") and adds the name of the current FSM
and the name of the state to be returned to. In this case it adds
"example6.Fruit" or "example6.Vegetable", depending on
whether the previous input was ``fruit'' or ``vegetable''.
- It then sets the "<fsmurl>" and "<fsmcurrentstate>"to ``/FSM/example6a/'' and ``Start'' respectively, i.e. to the
sub-network and to the Start state of that sub-network.
- And finally it generates the XML content containing these
values and returns it to the client.
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:
- _q_index gets the stack string (using
"self.doc.getFsmstack()" and splits it into a list of items.
- If there is nothing in this list (stackItems), it merely
returns a goodbye message, i.e. it assumes that it was called
directly and not as a sub-network.
- If there is at least one item on the stack, it pops an item
off the right end, splits the item into FSM name and state name,
and sets the "<fsmurl>" and "<fsmcurrentstate>" elements
with those values.
- And, finally, it generates the XML document to be returned to
the client.
A few comments:
- No state on the server -- State information is maintained by
passing an XML document back and forth between the server and
client. No session information is maintained by the server.
- Everything is a resource -- Each request to the server is a
request for a resource. This one does seem questionable. At the
least, what is being requested is only a resource in an abstract
sense. A request is a request for an action primarily and for data
secondarily. Each request to an FSM, as we have construed and
implemented them is a request that the FSM chose a transition,
perform an action, and select a next state. Data (a representation
in REST terms) is produced and returned only as a side-effect.
Given this concept of a resource, the patterns we have described do
request a resource, an action, a change of state, and the
production of data/representation.
- Every resource has a name -- In some of our examples (e.g.
examples (example1) and example3), the same name
(URI) is used for every request and the current state is carried in
the XML interchange document. In other examples (e.g. examples
example2 and example4), the current state is the
last component of the URI.
- Requests are idempotent -- It is idempotent in the sense that
we can submit any request twice with no harmful effect. (Note that
this is slightly different from the standard definition of
``idempotent'', which is more to the effect that subsequent
requests have exactly the same effect.) And, implementing this
sense of idempotency is a burden for the server. The server must
ensure that, for example, submitting the same request twice will
not cause harmful effects.
I'm arguing that we have solutions to the following problems:
- How to cast complex procedures into a REST style -- The
solution we present implements a complex process as an FSM network
on top of Quixote and does so in a REST-ful style. So,
our strategy is to translate a complex process into an FSM and then
to implement the FSM in a REST-ful style.
- How to implement an FSM in a REST-ful style -- The examples
and patterns that we have given above, show how to do this.
- How to ``call'' a sub-network -- We model this solution on RTNs
(recursive transition networks) and we use an element in the XML
interchange document to store the return stack.
End Matter
Thanks to the implementors of Quixote for producing an
exceptionally usable application server that is so well suited for
REST.
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.