In this series of articles, we are looking at how it is possible to
extend the basic principles of service-oriented architecture (SOA)
to devices at the edge of the network (such as cell phones, PDAs and
set-top boxes) so that they can connect to enterprise-level
services within a network.
In "Part
1: Thinking Mobile ," we described how interfaces to network
services could be defined and implemented at a level of abstraction,
regardless of the mobile network or underlying network protocol
layers. This means that we can "talk" to network services directly
from any cellular phone that has a standard Java implementation
(Java ME) without any concern over whether we are using 2G, 2.5G, or
3G mobile networks, or whether the underlying network protocol is iMode,
WAP, or HTTP.
In this article, we are going to extend the facilities of our
architecture by adding the ability for devices to become involved in
atomic transactions. Transactions are vital in ensuring that all of
the relevant resources that a system provides are coordinated and
managed so that the overall state of the system remains
consistent.
The benefits of using transactions can range from the simple, such
as making sure that your seat at the movie theater is not double
booked, to the essential, such as making sure that the correct piece
of medical equipment can be provided to the correct field hospital
in a disaster zone.
Introduction
For the architecture we are designing, we need transactions to be
both distributed
and mobile--which, in simple terms, means that a transaction
represents a "unit of work" between a set of cooperating services on
the network and over the air.
Under a transaction, either:
- The unit of work is executed as a whole: For example, your airline flight reservation is confirmed and your
credit card is debited.
- None of the actions are completed: Your airline flight reservation is rejected because the
flight has just been fully booked, and so your credit card account is
not debited.
In a SOA, a transaction manager is usually a service that is used
to coordinate the other services participating in the
transaction. It does this by creating the transaction context and
then coordinating the participating services. Normally, this is
implemented using
a two-phase
commit model in which a commit is performed on all of the
participants, or an abort is performed on all of the participants,
giving us the "all or nothing" behavior that we are looking for.
If we are going to implement services such as mobile banking or
logistics and supply chain applications, then we need to devise a
method of making our mobile devices capable of being involved in any
transaction, either explicitly by talking directly to the
transaction manager, or implicitly through calls to services that
are themselves transactional. Or, for maximum flexibility, both.
Using Transactions on a Device
Our mobile SOA has targeted the lowest common
denominator for Java on devices, namely MIDP/CDLC, which means that
any more powerful Java-enabled device can also take advantage of the
architecture. When we took at look at designing in the support for mobile
transactions, we settled on two sets of interfaces that are used
on the device to create, abort, and commit transactions, and also to
participate in transactions.
We also needed to implement code to manage leases on transactions
to set time limits on operations. This means that if parts of the
system fail, we can abort the transaction after a predetermined
amount of time. For example, if a mobile client becomes disconnected
when your train goes through a tunnel and the network connection
cannot be maintained, the transaction manager will abort the
transaction when the lease expires.
If the transaction is aborted,
all of the active participants for that transaction will be
notified. As a result your business services can release any
resources that had been "locked" for the expired transaction.
To recap: mobile clients can either be explicitly or implicitly
involved in any transaction.
Mobile Movie Reservations
For a simple example, our local movie theater has given us a brief
to design a prototype mobile seat booking application, so that
moviegoers can book a ticket to their favorite movie and reserve a
seat on the way to the theater. This type of system inherently
requires transactions to make sure that seats are not double booked,
and that should a booking fail, the customer is not charged.
On the device, we access the transaction manager in order to create
a transaction, and then use that transaction object to pass the
business services participating in the transaction.
//MIDP code - using explicit transactions
//Obtain proxies for the Transaction Manager & the business service
TransactionManager txnMgr= TransactionManagerFactory.getService();
SeatReserver seatReserver = SeatReserverFactory.getService();
//creating a transaction with a five minute lease
long lease= 60 * 1000 * 5;
Transaction txn=txnMgr.create(lease);
//register the transaction with the business service
seatReserver.newTransaction(userDetails,txn);
//user has selected the date, the movie and the seat(s)
seatReserver.reserve(dateTime, movieID, seatID ,txn);
//when all operations are complete, prompt the user to commit the transaction
txnMgr.commit(txn);
In the code example above, both the TransactionManager
and SeatReserver are "shadow" proxies (to remote
services) created with the mobile stub generator, which takes a
service interface as an input and generates the code required for a
mobile client to communicate with a remote service. This is
described in detail
in part
one of this series.
Having obtained the proxies to the two services, the code then
obtains a Transaction object from
the TransactionManager with a lease of five minutes,
meaning that the client code has to complete all operations using
the transaction within that timeframe; otherwise, the transaction
manager will abort the transaction.
The transaction object is then passed to
the seatReserver along with user's details, to allow the
service to register as a participant in the transaction. More about
that later.
In this example, the SeatReserver service forms part of
the remote SOA that provides access to a billing service, which in
turn participates with the transaction created on the mobile
client.
An alternative approach would have been to encapsulate the usage of
a transaction within the business service itself and have a method
on the service to commit the transaction internally; for example, a
"purchase" method.
Participating in a Transaction
In the above example, the mobile client code is responsible for
committing or aborting the transaction. Let's now take a look at
how your business services gets notified.
The architecture we have designed provides a simple model that allows
you to connect adapters to your existing business services. This
means that you do not need to make any modifications to those
services, but just simply write the connector code--this gets
loaded at runtime by the gateway.
In our design, the transaction manager is only responsible for
managing the two-phase commit protocol, so your business service (or
connector code) needs to implement
the TransactionParticipant interface to be able
register as a participant with
the TransactionManager.
In the earlier client example, the mobile application first created
a transaction and then passed that transaction to
the SeatReserver service, by invoking the
method seatReserver.newTransaction(). Let's put all
the pieces together and take a look at how
the SeatReserver service actually interacts with the
transaction manager.
//Service code example to participate in a Transaction
public class SeatReserverImpl extends AbstractTransactionParticipant{
private TransactionManager transactionManager;
public SeatReserver(TransactionManager txnMgr){
transactionManager=txnMgr;
}
public boolean prepare(Transaction txn){
//return true if this participant to ready to commit
}
public void commit(Transaction txn){
//code to actually commit the transaction
}
public void abort(Transaction txn){
//The transaction has been aborted
}
//methods invoked from mobile client
public void newTransaction(UserDetails user,Transaction txn){
//once you have joined the transaction the TransactionManager will
//be able to invoke methods on the TransactionParticipant methods,
//commit, prepare and abort
transactionManager.join( txn, this/*TransactionParticipant*/);
}
public void reserve(Date dateTime,long movieID,long seatID,Transaction txn){
//store data keyed on the transaction's ID
//later this data is either committed to a database
//or aborted depending on the final state of the transaction
}
}
The diagram in Figure 1 below shows the relationships between the
transaction manager and the SeatReserverImpl class
shown above.

Figure 1. Transactions UML class diagram
Looking at the UML diagram in Figure 1, you will notice that
both TransactionManager
and TransactionParticipant directly implement the Java
RMI interface
Remote, whereas the SeatReserver indirectly
implements Remote by extending
the AbstractTransactionParticipant. This is because
the TransactionManager runs as Java RMI service as part
of the overall SOA, which allows for a mixture of RMI, Jini, and
user-defined connectors that can be exposed as services.
Here is a look at the whole architecture from
the mobile client's perspective:

Figure 2. Mobile transaction architecture
In Figure 2 above, you can see that the mobile device is using a
"transaction pack." This is a class library containing
prebuilt stubs that can communicate with the remote transaction
manager. The transaction manager is loaded as a connector service by
the gateway, thus making it available (via an RMI lookup) to other
business services running on the network.
Summary
Transactions are vital to ensure that all of the relevant
resources that a system provides are coordinated and managed so that
the overall state of the system remains consistent. A transaction
represents a "unit of work" between a set of cooperating services on
the network and over the air, which is either executed or aborted as
a whole. Transaction participants register an interest in a
transaction with a transaction manager. The transaction manager is
responsible for coordinating transaction participants using the
two-phase commit protocol.
In a SOA where services are inherently distributed and decoupled,
client applications may need to be able to interact with multiple
services, all within a single transactional unit. Leases are used to
ensure that transactions can be automatically aborted if the client
crashes, or fails to commit or abort the transaction. Simple
transaction-based systems can be implemented completely by the remote
services when these services are tightly coupled. The choice of whether
the client application or a remote service creates and
commits/aborts a transaction is determined by the required functionality of
the system you are building. The ability to control the state of a
transaction for either a mobile client or business service (or both)
provides maximum flexibility.
Resources
Code examples from this article
Net Caboodle
documentation
J2ME
Wireless Toolkit
Eclipse plugin
and mobile stub generator
Eclipse J2ME plugin
NetBeans 4.2
plugin and stub generator
NetBeans
version 4.2 (dev build) and NetBeans 4.2 Mobility Pack
"NetBeans
Mobility Pack Quick Start Guide "
JMX JConsole mobile
demo
Nigel Warren is a co-founder of
Net Caboodle and co-author, with Philip Bishop, of the books
Java in Practice and
JavaSpaces in Practice, both published by Addison Wesley Longman.