Introduction
This is the long awaited third part in the series of articles describing
a generic server-side architecture for transactional eCommerce websites,
using a Model View Controller architecture or Mediator Pattern [Gamma
95] configuration.
Part 1, outlined the architecture to be used to handle the event
driven nature of an http request - html response, server to client
system, and detailed how to deliver this in a 3 tiered model of
Presentation Layer, Business Layer and Persistence Layer. The Presentation
Layer is modeled using UML Statecharts [Horrocks
98] which lend themselves to easy implementation using Mediator
Pattern which provides the View and Controller elements of the MVC
model.
Part
2, explained how to implement the infrastructure to run the
MVC model using UML Class Diagrams [Coad 99],
Statecharts and Java code to process and respond to http request
events.
In
Part 3, we look at how all this hard work developing infrastructure
can be made to pay off and deliver real advantages. We look at how
to protect the system from unexpected browser (client-side) events
or activities such as refresh, back, and bookmarks. We will see
that this can be achieved with a single piece of code to manage
all of this activity.
We
will also see how Statecharts can be used to cleanly model the boundaries
of logical transactions and how the MVC engine can be used to provide
protection of these transaction boundaries (transaction isolation
[Gray 93]) and scoping using a single piece
of code.
Finally
we will look at how exceptions to normal operation can be cleanly
handled and implemented within the same system.
[To completely follow the discussion in this paper, you may find
it helpful to refer to the diagrams from Part
2]
Protection from browser functionality
Current State And Session
The Current State and the Session ID are encoded into the URL with rewriting
e.g. my.home.com/Connector?sessionID=12345;state=4.2.1 This guarantees
that we will always see the Session ID and Current State at the
server when we receive a request.
The
Connector Class is arranged to hide the incoming request inside
an IHTTPRequest interface implementor class which will turn the
parameters into a nice set of name value pairs. The Connector class
must then pass that IHTTPRequest off to the RequestManager class
which is then responsible for identifying the appropriate State
Object e.g. the State with name="4.2.1", and the appropriate
LoginSession Object e.g. the LoginSession with ID="12345".
For
security reasons it is advisable that Session IDs are randomly generated
and contain some check digit. A Class method in LoginSession can
be provided to validate the authenticity of the Session ID. If it
fails then a suitable exception can be thrown by the code. An alternative
scheme might involve encoding the Users name or ID along with the
Session ID and validating one against the other. The idea is to
make it sufficiently difficult for a hacker to guess the Session
ID while a session is "live". It should also be near impossible
for Session IDs to be modified in the URL and give instant access
to someone else's session. Such goofs in security have been seen
over the last year on several eCommerce sites, particularly those
of online banks.
Refresh Button
On
the event of a Refresh or Reload request from the client, we receive
the Session ID and the Current State because they are encoded in
the URL. Our HTTPRequest Class will translate this into a nice set
of name value pairs. Our RequestManager Class will in turn see that
we have no Event with this request. Therefore, its a request to
deliver an existing State back to the client. That existing State
will be held within the collection of StateVisits at the LoginSession.
So, we ask to revisit the State by calling revisitState() on the
LoginSession Class. This will identify the State object in its collection
of StateVisits. If the State is not in the collection then it will
throw an exception which we can catch back in RequestManager. Revisiting
a State is a simple matter of rebuilding the html and streaming
it to the client, based on the Persistent Parameter which we stored
at the server attached to the appropriate StateVisit object. The
result of this is that we can rebuild any given screen on a reload
request and we don't require all the previous parameters to be encoded
in the URL. The Session ID and State ID are enough. This shows that
the MVC Engine architecture has a huge advantage. We have a single
piece of code protecting the whole application environment from
use of the Refresh key at the browser.
Back Button
Another big fear for stateful client systems is the Back Button. The client
is able to change the client State without the server being aware.
Well, it ought to be obvious that the mechanism for protecting against
Refresh works equally well for the Back Button with a subtle difference,
there is usually an event (unless Back followed by Refresh has been
used). With the Back Button scenario, the client has pressed back
several times. Let us suggest that the server believes the current
state to be State 7. We receive a request and that contains StateID
= State3, SessionID = 1234, Event = EventB. First of all we ask
the LoginSession whether State3 is in its collection. If it is not
then we throw an exception i.e. the Back Button hadn't been used,
someone was bookmarking or editing the URL or hacking our system.
Assuming that State3 is in the system, we now override the current
state, setting it back to State3. Now working on the basis that
we've been in State3 all along, we simply process the EventB in
the normal fashion - see Web MVC Part2.
So our Server-side MVC Engine has given us a single place, a single
piece of code, which protects the whole application from use of
the Back Button. What about an open Database Session or Transaction,
you might ask? Well that is a good question which we will revisit
later in this article.
Bookmarking
The
use of bookmarks to "jump" into the middle of a website is another
headache for a web based application. Again our Web MVC engine is
well placed to cope with this problem. Remember that the SessionID
and StateID will be encoded in the Bookmark. We have two choices
when we receive a request from a bookmark. We can choose to ignore
it. Simply we look at the SessionID which will be for an invalid
Session more often than not, and we simply throw an exception. RequestManager
will catch this and request output of a page saying something like,
"Please Login".
However,
it could be that the Session ID is valid. The User may still be
in the same Session. In this case, we can treat the problem in a
similar fashion to Back Button and Refresh. The bookmarked state
may already be in the collection of StateVisits, in which case,
simply call revisitState() on the LoginSession class. If the State
is new then you will need to call gotoState(). There are possible
complications if a transaction boundary or open database session
is involved. However, this will be tested and caught by the same
transaction scoping code already mentioned and discussed below.
Transactions
There
is a second type of behavior which we need in a navigation mechanism,
other than the Condition Evaluation we have just shown. This second
type of behavior is known as an Action. An Action is something which
must happen after an Event transition takes place but just before
the new State is entered.
One
key use for Actions is the scoping of Database Transactions. Typically,
the Begin Transaction, the Commit and the Rollback of a transaction
will happen as Actions. The ability to manage transactions is fundamental
to eCommerce Websites and Application Service Providers and Extranet
Sites etc.
Here
is a basic example of how we can model the Transaction Boundaries
onto a Statechart. Note the Transaction Boundary is not part of
the currently adopted UML Statechart notation but is shown as an
aid to understanding.

Diagram1. The Transaction Boundary for a Conversational Tx in an HTML Website
It
is important that a Transaction only have a single starting point.
A discussion of why this is so is out of the scope of this paper.
The Starting point is given when the First Page is entered. An Action
is set on the initialization of the FirstPage State, which will
cause a Database Transaction to begin. The appropriate server-side
script would be run to make this happen.
The
other important Actions are the Commit and the Rollback. Commit
or Rollback must happen when the navigation crosses outwith the
Transaction Boundary. The Commit Action is set to happen when the
Navigation moves to the Good Exit State. In other words, the Name,
Address and Credit Card Number have all validated correctly. The
Rollback Action will happen if any of the earlier parameters were
not valid. [Note: again this is a simplified example]
Now
consider how the Transaction Boundary will look on the WML Version

Diagram2.
The Transaction Boundary for a Conversational Tx in a WML Website
Now
let's consider how we might represent the transaction boundary in
the data model and what we might do to enforce the transaction isolation.
[We are making a big assumption here - single transaction per client
at any one time and no nesting of transaction, though in principle
nesting would be possible].
The Object model to support the data for the transaction boundary
would look like this.

Diagram3.
Updated Class model fragment with Unit of Work Class
The
reference from the LoginSession is holding the "current"
Unit of Work for the client. The relationship is either 0 or 1.
The client with this Login Session is either in a conversational
transaction or they are not. A method can be added to LoginSession
called isInTransaction() which returns a Boolean answer. We will
use this value in the RequestManager class for processing possible
exceptions related to transactions.
So
now let us consider the rules for ensuring the transaction isolation.
1.
Staying out of a transaction
If
the LoginSession is NOT in a transaction ( isInTransaction ==
false )
and the destination (Goto) State is NOT in a transaction then
gotoState(). Proceed as usual.
2.
Staying inside a transaction
If
the LoginSession is IN a transaction and the destination State
is IN the same transaction ( validate this with a getName on both
UnitOfWork objects or equivalency test on the ObjectID of the
UnitOfWork ) then gotoState().
If
the UnitOfWork equivalency fails then you must check for a valid
exit and a valid entry into the new transaction (see below).
3.
Entering a Transaction
If
the LoginSession is NOT in a transaction and the destination State
in IN a transaction then validate that the destination state is
the "startState" of the Unit of Work i.e. only enter
by the allowed front door. If so then gotoState() and set the
LoginSession to UnitOfWork reference.
If
not entering by the UnitOfWork startState then throw new IllegalTxEntryException();
4.
Exiting a transaction
This
is slightly more tricky as their can be multiple legal exits.
If
the LoginSession is IN a transaction and the destination State
in NOT in a transaction and the transition between states is a
legal one then gotoState(). Proceed as usual. The code to tidy
up the transaction should be written explicitly as an Action on
the StateTransistion.
If
the transition is not a legal one, then by some means the client
has exited the transaction, perhaps through use of the Back button.
In this case, explicitly rollback() the transaction and proceed
to the new State.
If
the destination state is in a different Unit Of Work and the transition
is a legal one then determine that the destination State is the
"startState" for the new Unit of Work and proceed. Otherwise,
throw new InvalidTxException();
If
the destination state is in a different Unit of Work and the transition
is not a legal one then first of all explicitly rollback() the
current transaction and then determine whether the destination
State is the "startState" for the new Unit of Work.
If so proceed. Otherwise throw an exception.
All
of these rules would be written in to the RequestManager class and
executed just prior to it executing the gotoState() method on LoginSession.
Exceptions
Catching Exceptions
Generally
speaking all exceptions in the MVC Engine should be propagated up
to the RequestManager class which should have an extensive try{}
catch{}; block.
As
was shown above, a relatively fine grained approach to exception
naming works best as it gives finer control. Groups of exceptions
to be processed together can be grouped using inheritance if necessary.
For example, all Transaction Isolation related exceptions might
inherit from TxIsolationViolationException. This may or may not
be useful depending on your design.
I
have also found it useful to have two basic categories of exception:
recoverable; and non-recoverable. Recoverable implies that the current
session can continue but something undesirable may have happened.
For example, the Credit Card verification mainframe is off-line.
This is a temporary fault and nothing that should end the client
session. In theory there could be different levels of these. The
new page may have pieces missing, the client might be pushed back
to a previous page, or moved out a level within the site hierarchy,
their current Unit Of Work may have to end, and so forth.
A
non-recoverable exception implies that the client session must die.
You may also find it helpful to identify two categories of non-recoverable
exception: non-critical; and critical or catastrophic. Non-critical
generally implies that the current user session has to disappear
possibly because that user is doing stuff that just isn't allowed
or possible. It could be a security breech, a hacker attack or similar.
The catastrophic type of exception implies that the whole web server
may be unstable. Perhaps the processor has run out of swap space
or working memory. In which case, the whole website may need to
go off-line.
The
design of a complete exception strategy is really outwith the scope
of this paper.
Processing Exceptions
With
a fine grained strategy it should be fairly easy to identify the
condition the system was in when the exception was thrown. Other
clues such as the LoginSession, the current State and so forth can
be used. All necessary clean up for the current state should be
done in the catch{} block. This may include rolling back a transaction,
freeing temporary memory, etc.
The
final act in the catch{} block should be to try and recover the
situation. The recovery point is really determined by what exception
was thrown and where it happened i.e. what state the system was
in when it happened.
BrĂan O'Byrne and I have proposed an extension to the Statechart notation
to allow for the modeling of recovery of exception states.

Diagram4.
A Statechart Diagram with Exception State Extension
In
this diagram we have used a notation similar to the existing History
State notation, where an X is encased in a circle to denote the
Exception State. The condition evaluation on the transition to a
recovery state contains the name of the Exception which has been
caught. It could also include Actions such as a rollback() on a
transaction.
These
resumption states can be hard coded into the catch{} block or soft
coded through a resource file. Note that soft coding assumes that
the system is sufficiently "alive" to be able to load
the appropriate parameter from a file. Hard coding can have its
advantages when you are dealing with exceptional conditions.
In
the example above where we catch CCNumInvalidException, the last
instruction inside the catch block would be a LoginSession.gotoState("State3").
This would have the effect of recovering the client session to the
View associated with State 3 after it had done any necessary clean
up.
Summary
This series of articles has intended to demonstrate that a rigorous
approach to eCommerce web site design is possible. It has set out
to show that appropriate modeling of the Presentation Layer can
improve the robustness of the code and will improve the shared understanding
of how the code works.
Modeling
a Presentation Layer using UML Statecharts significantly aids the
effort of designing a robust system which can handle conversational
transactions, withstand abuse and attack from the client end, and
handle the processing of exceptional conditions appropriately.
There
is always an argument, particularly from programmers, that developing
and maintaining such detailed designs is a waste of time and costly.
I would advocate that when done with the use of a tool such as Together,
the maintenance of the diagrams is a relatively low overhead activity.
The cost in time upfront to develop and maintain the Statecharts
is easily outweighed by the cost at the back end on debugging and
re-working faults. Naturally there is a caveat. This approach becomes
appropriate as the scale of the project grows. On a small site,
you may never see pay back. On a medium sized site, you certainly
will and on a large site your project may be in danger of complete
failure if you do not follow an approach like the one described
in these articles.
The
design described in these 3 pages was loosely intended for use with
Oracle Application Server 4.x with cookies turned off. Your choice
of application server and environment will affect how much or how
little of the MVC engine infrastructure that you have to build.
Many environments are now supporting conversational transactions
and nested transactions. Support for exception handling may not
be so good.
When
implementing a Presentation Layer, you must determine how much of
the infrastructure you need to build and how much is provided by
your choice of application server. Nevertheless, modeling the Presentation
Layer with Statecharts and implementing that Presentation Layer
using a Mediator pattern to process State Transitions through Events
is a strong, robust and controllable approach which will lead to
reduced bug count and easier debugging. It also lends itself to
better isolation of problems.
This
third part has shown where the real strong benefit lies in developing
a rigorous UML Statechart modeling approach to the Presentation
Layer. The browser exceptions such as Back Button, the Transaction
scoping and isolation and finally the server-side exception handling
are all neatly contained in one place, the RequestManager class.
You only have to write that code once and you only have to maintain
and debug it in one place. It can then be used for the whole implementation.
That's a big win.
References
[Horrocks 98] Constructing
the User Interface with Statecharts, Addison Wesley, Ian Horrocks,
1998
[Gray 93] Transaction
Processing : Concepts and Techniques, Jim Gray and Andreas Reuter,
Morgan Kaufmann, 1993
[Gamma 95] Design
Patterns : Elements of reusable Object-Oriented Software, Gamma,
Helm, Johnson, Vlissides, Addison Wesley, 1995
[Coad 99] Java
Modeling in Color with UML, Coad, De Luca, Le Febvre, PTR-PH,
1999