Blue Elephant Referee Scheduler
Transactions
Intro:
One
of the challenges in writing a web application is correctly handling transaction,
in order to keep the application in a consistent state. When several actions need to performed, only
two scenarios are possible: all will succeed or all will fail. Our approach
uses the following concepts, each of which is explained below:
·
The
Transactional Context and Threadlocal patterns
·
Hibernate and
JDBC transactions
Motivation:
The
motivation here is primarily correctness. We began with a desire to avoid
needing to use a full-blown J2EE/EJB application server, and chose the above
tools and patterns after some investigation of the following books:
Usage:
The
transactional context pattern is based on the following observation: transactional API’s generally provide support
for ‘database level’ transactions. That is, each time you go to the database,
your actions can be treated atomically.
However, what you usually want is Use
Case Level transactions,
which are higher level, so that all of the actions that comprise a use case
scenario succeed or fail atomically. Our
implementation is taken from chapter 10 of J2EE Design Patterns, and is
pretty slick. This chapter should be
considered a must-read before diving into the model area of our
application.
Some
key points, along with a running example, are shown below :
- Use case scenarios
correspond 1-1 with methods in our Business
Delegate classes, which present a
coarse grained application interface to the front end components. For example, we have a business delegate
with a resetPassword() method, which corresponds to the reset
password use case.
- Business
Delegates are created using a
factory object. This factory actually returns a proxy object that stands
in for the delegate. In order to enforce that delegates are created using
the factory, their constructors have package visbility, which prevents
front end components from instantiating them directly. In our example, the resetPassword()
method is a member of the UserManagementDelegate class. A front-end client obtains a reference
to such a delegate as follows:
UserManagementDelegate del =
BusinessDelegateFactory.getUserManagementDelegate();
What is returned is actually a proxy, but this is mostly transparent to the
client.
- This proxy object
transparently wraps each method invocation on the business delegate in a
transactional context – that is, a transaction is begun by the proxy
before the method call is passed through to the underlying delegate. If
the delegate method succeeds, the proxy commits the transaction. If the
delegate method throws an exception on the other hand, the proxy rolls the
transaction back. In our example, a front-end client would make the call:
del.resetPassword(user, newPassword);
- This method has two responsibilities: To update the user’s
password in the datbase, and then to send an e-mailnotifying the user of
the change. Either both actions
need to succeed, or neither should succeed. This is where this pattern shines. The business delegate contains no code
to handle the transaction, because it is taken care of by the
transactional context proxy.
- Because proxies are built using Java
reflection, which requires interfaces, all business delegate objects must
have corresponding interface classes that declare all public methods
provided by the business delegate. If you add a public method to a
business delegate object, but not to its corresponding interface, it will
not work. In our running example, the front-end client programs
against the UserManagementDelegate
interface,
which is implemented by a class called UserManagementDelegateImpl.
- Business delegate
methods generally call through to DataAccessObjects, which encapsulate the
data access methods (generally implemented using Hibernate in our
case). These methods need a way to
join an in-progress transaction. We
accomplsh this by using a Singleton utility class called HibernateUtil, which implements the Threadlocal pattern.
DataAccessObjects obtain a Hibernate session object from HibernateUtil. HibernateUtil manages one session object per thread using
the Java threadlocal type, so that concurrent requests each use a separate
transaction. Our example is
slightly broken here, because the action of sending an e-mail does not
participate in the transaction directly.
We accomplish the same effect, however, by making it the second of
the two actions. In the first
action, we reset the password in the database. If this fails for any reason,
the e-mail is not sent. If it
succeed, then we try to send the e-mail. If this fails for any reason, an
exception is thrown, and the proxy rolls back the transaction, which
nullifies the change of password.
Links:
The primary reference here
is Chapter 10 of J2EE Design Patterns.
Chapter 9 is also quite useful as an explanation of the Business
Delegate pattern. This book is available
online via Safari, which is free for
OSU students.