The class diagram in Figure 4.3shows this sort of design.In this design, the objects that the transaction manipulates need notcontain their own instance data.. The Read/Write Lock patter
Trang 1The simplest way to be able to restore an object’s state after a failedtransaction is to save the object’s initial state in a way that it can easily be
restored The Snapshot pattern (discussed in Patterns in Java, Volume 1)
provides guidance for this strategy Figure 4.2 is a class diagram thatshows this general approach
An object in the role of transaction manager manipulates instances ofother classes that participate in a transaction Before doing something thatwill change the state of an object that it manipulates, the transaction man-ager will use an instance of another class to save the initial state of theobject If the transaction manager’s commitmethod is called to signal thesuccessful completion of a transaction, then the objects that encapsulatethe saved states are discarded However, if the transaction manager detects
a transaction failure, either from a call to its abortmethod or from theabnormal termination of the transaction logic, then it restores the objectsthat participate to their initial state
If it is not necessary to save an object’s state beyond the end of the rent program execution, a simple way to save the object’s state is to clone it.You can make a shallow copy* of an object by calling its clonemethod
cur-42 ■ CHAPTER FOUR
FIGURE 4.2 Saving state for future recovery
* A shallow copy of an object is another object whose instance variables have the same values
as the original object It refers to the same objects as the original object The other objects that it refers to are not copied.
TransactionParticipantClass1 TransactionParticipantClass2
Manipulates 1
commit( )
▲
▲
Trang 2All classes inherit a clonemethod from the Objectclass The clonemethod returns a shallow copy of an object if its class gives permission forits instances to be cloned by implementing the Cloneableinterface TheCloneableinterface is a marker interface (see the Marker Interface pat-tern in Volume 1) It does not declare any methods or variables Its onlypurpose is to indicate that a class’s instances may be cloned.
In order to restore the state of an object from an old copy of itself, theobject must have a method for that purpose The following listing shows
an example of a class whose instances can be cloned and then restoredfrom the copy.*
class Line implements Cloneable {
private double startX, startY;
private double endX, endY;
private Color myColor;
public Object clone() { super.clone(); }
public synchronized void restore(Line ln) {
method
If you need to save and restore instances of a class that does not have
a public clonemethod and it is not possible for you to add a public clonemethod to its instances, then you will need an alternative approach Onesuch approach is to create a class whose instances are responsible for cap-turing and restoring the state of the objects lacking a clone method byusing their publicly accessible methods
For saving the state of an object whose state is needed indefinitely, asimple technique is to use Java’s serialization facility A brief explanation
of how to use serialization is shown in the sidebar
Saving the initial state of the objects a transaction manipulates is notalways the best technique for allowing their initial state to be restored If
* At the beginning of this chapter, I stated that there would be no code examples The code examples that appear here are implementation examples and not examples of the pattern itself.
Trang 344 ■ CHAPTER FOUR
Serialization
Java’s serialization facility can save and restore the entire state of anobject if its class gives permission for its instances to be serialized.Classes give permission for their instances to be serialized by imple-menting the interface java.io.Serializable, like this:
Import Java.io.Serializable;
class Foo implements Serializable {
TheSerializableinterface is a marker interface (see the
Marker Interface pattern in Patterns in Java, Volume 1) It does not
de-clare any variables or methods Declaring that a class implements theSerializableinterface simply indicates that instances of the class may
To create an ObjectOutputStreamobject that serializes that state
of objects and writes the stream of bytes to a file, you would write codethat looks like this:
FileOutputStream fout = new FileOutputStream("filename.ser");
ObjectOutputStream obOut = new ObjectOutputStream(fout);
The code creates an OutputStreamto write a stream of bytes to afile It then creates an ObjectOutputStreamthat will use the Output-Streamto write a stream of bytes
Once you have created an OutputStreamobject, you can serializeobjects by passing them to the OutputStreamobject’s writeObjectmethod, like this:
ObOut.writeObject(foo);
transactions perform multiple operations on objects that contain a lot ofstate information, and the transaction modifies only some of the stateinformation in the objects, saving the object’s entire state information iswasteful
In this situation, a more efficient implementation approach is based
on the Decorator pattern described in Volume 1 The technique is to leave
the original object’s state unmodified until the end of the transaction anduse wrapper objects to contain the new values If the transaction is suc-cessful, the new values are copied into the original object and the wrapperobjects are then discarded If the transaction ends in failure, then the
Trang 4wrapper objects are simply discarded The class diagram in Figure 4.3shows this sort of design.
In this design, the objects that the transaction manipulates need notcontain their own instance data Nor do they need to implement the Trans-actionParticipantIFinterface Instead, a separate object contains theirinstance data To ensure strong encapsulation, the class of the objects thatcontain instance data should be an inner class of the class of the manipu-lated objects
When a transaction manager becomes aware than an object will beinvolved in a transaction, it calls the object’s startTransactionmethod
ThewriteObjectmethod discovers the instance variables of anobject passed to it and accesses them It writes the values of instance vari-ables declared with a primitive type such as intordoubledirectly to thebyte stream If the value of an instance variable is an object reference, thewriteObjectmethod recursively serializes the referenced object
Creating an object from the contents of a serialized byte stream is
called deserialization To deserialize a byte stream, you need an InputStreamobject You can use an ObjectInputStreamobject toreconstruct an object or restore an object’s state from the state informa-tion stored in a serialized byte stream
Object-To create an ObjectInputStreamobject, you can write some codethat looks like this:
FileInputStream fin = new FileInputSteam("filename.ser");
ObjectInputStream obIn = new ObjectInputStream(fin);
This code creates an InputStreamto read a stream of bytes from a file
It then creates an ObjectInputStreamobject You can use the InputStreamobject to create objects with instance information fromthe stream of bytes or restore an existing object to contain the instanceinformation You can get an ObjectInputStreamobject to do thesethings by calling its readObjectmethod, like this:
Object-Foo myObject-Foo = (Object-Foo)obIn.readObject();
ThereadObjectmethod returns a new object whose state comesfrom the instance information in the byte stream That is not quite whatyou need when restoring an object to its initial state What you need is
a way to use the instance information to set the state of an existing able You can arrange for that as you would for allowing an object’sstate to be restored from a clone You ensure that the class of the ob-jects to be restored has a method that allows instances of the class tocopy their state from another instance of the class
Trang 5vari-46 ■ CHAPTER FOUR
ThestartTransactionmethod causes the object to create and use a newdata object When the manipulated object calls one of the new data object’smethods to fetch the value of an attribute, if the data object does not yethave a value for that attribute, it calls the corresponding method of theoriginal data object to get the value
If a transaction ends in failure, then the transaction manager objectcalls the abortmethod of each of the manipulated objects Each object’sabort method causes it to discard the new data object and any values that
DataClass1
+getAttribute1 +setAttribute1 +getAttribute2 +setAttribute2
Buffers-changes
TransactionManager abort( )
commit( )
0 1
values
transaction- transaction- values
pre-Buffers-changes
DataClass2 -attribute1 -attribute2
+getAttribute1 +setAttribute1 +getAttribute2 +setAttribute2
data-for
commit( )
▲
data-for
Contains-▲
0 1
values
transaction- transaction- values 1
Trang 6pre-This design requires data values to be copied only if they are altered
by a transaction It may be more efficient than saving an object’s entirestate if the object contains a lot of state information that is not involved inthe transaction The disadvantage of this design is that it is more complex
Consistency
There are no implementation techniques specifically related to tency All implementation techniques that help to ensure the correctness
consis-of programs also help to ensure consistency
The most important thing that you should do to ensure the tency of a transaction is testing The Unit Testing and System Testing
consis-patterns described in Patterns in Java, Volume 2 are useful in designing
appropriate tests Using the Assertion Testing pattern, also described in
Volume 2, to ensure that a transaction’s postconditions are met can provide
additional assurance of internal consistency
Isolation
Isolation is an issue when an object may be involved in concurrent actions and some of the transactions will change the state of the object.There are a few different possible implementation techniques for enforcingisolation The nature of the transactions determines the most appropriateimplementation technique
trans-If all of the transactions will modify the state of an object, then youmust ensure that the transactions do not access the object concurrently.The only way to guarantee isolation is to ensure that they access the ob-ject’s state one at a time by synchronizing the methods that modify theobject’s state This technique is described in more detail by the Single
Threaded Execution pattern described in Volume 1.
If some of the concurrent transactions modify an object’s state, andothers use the object but do not modify its state, you can improve on theperformance of single-threaded execution You can allow transactions that
do not modify the object’s state to access the object concurrently whileallowing transactions that modify the object’s state to access it in only asingle-threaded manner This technique is described in more detail by the
Read/Write Lock pattern described in Volume 1.
If transactions are relatively long-lived, it may be possible to furtherimprove the performance of transactions that use but do not modify thestate of the object if it is not necessary for the objects to have a distinctobject identity You can accomplish this by arranging for transactions thatuse an object but do not modify the object’s state to use a copy of theobject The following patterns can be helpful in doing this:
Trang 7Ÿ The Return New Objects from Accessor Method pattern (described in
Volume 2)
Ÿ The Copy Mutable Parameters pattern (described in Volume 2)
Ÿ The Copy on Write Proxy pattern, which is used as an example in the
description of the Proxy pattern in Volume 1
In those cases where it is not possible to do these things, a long-lived action may tie up resources for an unacceptably long time This maynecessitate using some alternative strategies One possibility is to breakthe long-lived transaction into shorter-lived transactions Other strategiesinvolve giving up some of the ACID properties
trans-For example, you may allow other transactions that need a resourcelocked by a long-lived transaction to interrupt the transaction This is rea-
sonable when combined with another technique called checkpoint/restart.
Checkpoint/restart involves saving that state of transaction object at gic points when the transaction objects are in a consistent state When thetransaction is interrupted, the objects it manipulates are restored to theirmost recently saved state Later on, the transaction is restarted from thepoint where the states were saved
strate-Using this combination of techniques solves the problem of a lived transaction locking resources for an unacceptably long time at theexpense of losing the atomicity and isolation properties
long-Durability
The basic consideration for ensuring the durability of a transaction is thatits results must persist as long as there may be other objects that are con-cerned with the object’s state If the results of a transaction are not neededbeyond a single execution of a program, it is usually sufficient to store theresult of the transaction in the same memory as the objects that use thoseresults
If other objects may use the results of a transaction indefinitely, thenthe results should be stored on a nonvolatile medium such as a magneticdisk This can be trickier than it at first seems The writing of transactionresults to a disk file must appear atomic to other threads and programs.There are a few issues to deal with in ensuring this:
Ÿ A single write operation may be translated into multiple write tions by the object responsible for the write operation or the underly-ing operating system That means that data written using a singlewrite call may not appear in a file all at once
opera-Ÿ Operating systems may cache write operations for a variety of ciency reasons That means data written by multiple write operations
effi-48 ■ CHAPTER FOUR
Trang 8may appear in a file at the same time or it may be written in a ent sequence than the original write operations.
differ-Ÿ When accessing remote files, additional timing issues arise When aprogram writes information to a local file, the modified portion of thefile may reside in the operating system’s cache for some time before it
is actually written to the disk If another program tries to read themodified portion of a file while the modifications are still cached,most operating systems will be smart enough to create the illusionthat the file has already been modified If read operations on a filereflect write operations as soon as they occur, the system is said to
have read/write consistency.
Read/write consistency is more difficult to achieve when ing a remote file That is partially because there can be unboundeddelays between the time that a program performs a write operationand the time that the write arrives at the remote disk If you take nomeasures to ensure that access to a remote file has read/write consis-tency, the following sequence of events is possible:
access-1 Program X reads the file.
2 Program X performs a write operation.
3 Program Y reads the unmodified but out-of-date file.
4 Program Y performs a write operation.
5 Program Y’s write arrives at the file.
6 Program X’s write arrives at the file.
Read/write consistency can be achieved through the use of locks, butthat can seriously hurt performance
Ÿ An object that may read the same data from a file multiple times will pay a performance penalty if it does not cache the data to avoidunnecessary read operations When reading from a remote file,caching becomes more important because of the greater time
required for read operations However, caching introduces anotherproblem
If the data in a file is modified, then any cache that contains dataread from the file is no longer consistent with the file This is called
the cache consistency problem.
The following paragraphs contain some suggestions on how to deal with the problems related to the timing of actual writes to local files The Ephemeral Cache Item pattern explains how to handle cache consistency
It is not generally possible to control exactly when the data from awrite operation will actually be written to physical file However, it is possi-ble to force pending write operations to local file systems to complete This guarantees that all pending write operations have completed at a
Trang 9known point in time It is generally good enough for ensuring the durability
of a transaction unless the transaction is subject to real-time constraints.There are two steps to forcing write operations to local file systems tocomplete The first step is to tell objects your program is using to performwrite operations to flush their internal buffers For example, all subclasses
ofOutputStreaminherit a method named flush A call to the flushmethod forces the OutputStreamobject to flush any internal buffers that
it might have
The second step to forcing write operations to local file systems tocomplete is to get the FileDescriptorobject for the file your are writing.FileDescriptorobjects have a method named sync A call to a File-Descriptorobject’s syncmethod tells the operating system to flush anycached write operations for the associated file
All ACID Properties
An implementation issue that affects all four ACID properties is how tohandle a commit operation that is unable to successfully complete In allcases, the objects manipulated by the transaction must be left in a consis-tent state that reflects either the success or failure of the transaction
We are concerned about two failure modes One is that the commitoperation is unable to commit the changes made during the transaction,but the objects that are interested in the results of the transaction are aliveand well The other is a larger-scale failure that causes the commit opera-tion not to complete and also causes all of the objects that are interested inthe results of the transaction to die
The problem is simplest when the failure is limited to the commitoperation and the objects interested in the results of the transaction arestill alive and well In this case, since the commit could not succeed, thetransaction must fail All that is required is to restore the objects manipu-lated by the transaction to their state at the beginning of the transaction.The larger-scale failure presents an additional challenge if the objectsthat were interested in the results of the transaction will persist after thefailure Before processes or threads are started that will allow objects tosee an incomplete transaction, the incomplete transaction must bedetected and its commit must be completed or backed out
In summary, adding your own logic to an application to enforce ACIDproperties for transactions adds considerable complexity to the applica-tion When possible, use an available tool that can manage the ACID prop-erties for you
If you must create your own support for the ACID properties of actions, your design for each transaction will include some of the elementsshown in the class diagram in Figure 4.4
trans-50 ■ CHAPTER FOUR
TE AM
FL Y
Trang 10Here are descriptions of the roles classes play in ACID transactions asindicated in Figure 4.4:
Transaction Logic. Though there are many ways to organize thelogic of a transaction, the most common design is to have oneclass that encapsulates the core logic of a transaction This classmay encapsulate the core logic for multiple related transactions
TransactionParticipant1, TransactionParticipant2, The logicencapsulated in a TransactionLogicclass modifies the state ofinstances of these classes
TransactionManager. This class encapsulates reusable commonlogic to support atomicity For distributed transactions, it mayalso encapsulate the logic to support durability
TransactionLogicobjects use an instance of this class to age a transaction
man-FIGURE 4.4 Generic transaction classes
TransactionParticipant1
Contains-data-for
data-for 1
Manipulates
TransactionParticipant2
1 1
1
StateSavingClass1
1 StateSavingClass2
1
Uses
1
Uses 1 1
Trang 11TransactionParticipantIF. EachTransactionParticipantclassimplements this interface The purpose of this interface is toallow a TransactionManagerobject to manipulate Trans-actionParticipantobjects without having a dependency onany specific TransactionParticipantclass.
StateSavingClass1, StateSavingClass2, Classes in this role areresponsible for saving and restoring the state of Transaction-Participantobjects These classes are usually specific to a sin-gleTransactionParticipantclass or a small number ofrelatedTransactionParticipantclasses
ReadWriteLock. If concurrent transactions will be accessingTransactionParticipantobjects, with some transactionsmodifying an object and other transactions just requiring readaccess, an instance of this class is used to coordinate sharedread access and exclusive write access to the object Theseclasses are usually reusable
To conclude this discussion of implementing the ACID properties, if
it is at all possible to use an existing transaction manager, then do so Thedetails presented here for doing it yourself are complex and subtle Using
an existing transaction manager will generally produce better results
trans-Oracle uses an implementation of atomicity that is analogous to theimplementation using wrapper objects
RELATED PATTERNS
Snapshot. The Snapshot pattern (described in Volume 1) describes
techniques for saving and restoring the state of objects This isthe better way to recover from a transaction failure when a
52 ■ CHAPTER FOUR
Trang 12transaction involves a long sequence of operations that modifythe state of a small number of simple objects.
Command. The Command pattern (described in Volume 1)
describes techniques for remembering and undoing a sequence
of operations This is the better way to recover from a tion failure when a transaction involves a short sequence ofoperations that modify the state of a large number of complexobjects
transac-Transaction State Stack. The Transaction State Stack pattern may
be used to make a transaction’s changes to multiple objectsatomic
Audit Trail. Logging a sequence of operations to support theCommand pattern is structurally similar to maintaining an audittrail
System Testing. The System Testing pattern (described in Volume
2) should be used to ensure the consistency of transactions.
Unit Testing. The Unit Testing pattern (described in Volume 2) may
also help to ensure the consistency of transactions
Single Threaded Execution. The Single Threaded Execution
pat-tern (described in Volume 1) can be used to keep transactions
that modify the state of the same object isolated from eachother
Read/Write Lock. The Read/Write Lock pattern (described in
Volume 1) can be used to keep transactions that use the same
object isolated from each other while allowing transactions that
do not modify the object’s state to execute concurrently
Read/Write Consistency. If you directly manage the storage of sistent distributed objects, you may need the Read/Write Consis-tency pattern to ensure that data and objects that are read fromfiles are consistent with the most recent write operation
per-Ephemeral Cache Item. If you directly manage the storage of sistent distributed objects, you may need the Ephemeral CacheItem pattern to ensure that the result of a locally initiated readoperation matches the current contents of a remote store
Trang 14You want to design and implement transactions correctly and with a mum of effort Simple transactions are easier to implement and make cor-rect than complex transactions You should design and implement
mini-complex transactions from simpler ACID transactions
CONTEXT
Sometimes, you want to design a complex ACID transaction using existingACID transactions as building blocks Using existing ACID transactions tobuild a more complex transaction does not automatically give it the ACIDproperties Consider the following situation
You work for the IT department of a supermarket chain In addition
to having a number of stores that sell food, the company has a centralfacility that produces bread, cakes, and other baked goods for the stores.The IT department provides systems to support these activities:
•There is manufacturing software for the bakery Every evening it isfed the quantities of each item that each store will need for the fol-lowing day It produces reports telling the bakers how much of eachitem to produce and what ingredients to order for the following day
•There is transportation scheduling software Every evening it is fed thequantities of each item each store will need for the following day Itschedules trucks to transport baked goods to the stores It producesreports telling the bakers how much of each item to put in each truck.Currently, the amount of each product each store needs for the nextday must be keyboarded into both software applications This increaseslabor costs It makes data entry errors more likely, since there are twice asmany opportunities to make mistakes The costs of data entry errors arehigher because they can lead to baked goods being produced but notloaded onto a truck or too many trucks being scheduled
You have the task of creating a mechanism that allows the data to beentered only once You think of writing a data entry program that will putthe data in the appropriate database table of each application Though youknow that you can make it work, you search for another way Because the
Composite Transaction
Trang 15program would assume the internal structure of other applications, youare concerned about maintenance problems later on.
Reading each application’s documentation, you find that they bothhave an application program interface (API) to programmatically presentdata to each application Transactions initiated by the APIs have the ACIDproperties This gives you a way to build the data entry mechanism usingonly supported features of the applications
The fact that both APIs support the ACID properties greatly simplifiesthe task of building a composite data entry transaction with a predictableoutcome By creating a composite transaction that simply invokes each API,you get a transaction that is consistent and durable without doing anythingelse However, you must carefully consider how to ensure that the compositetransaction is atomic and isolated They will generally not be atomic or iso-lated You must either take additional steps to make them so or determinethat a less stringent guarantee of their behavior is sufficient Without properattention to these details, transactions can be lost, they can be applied multi-ple times, or concurrent transactions may corrupt each other
The composite transaction in the example is not automaticallyatomic That is not a problem, for two reasons
•Before the transaction runs, the quantity of all baked goods scheduled
to be produced for a store is zero For that reason, there is no need tosave the old values before the transaction You can back out thetransaction by setting all of the values to zero
•Both the component transactions are idempotent Idempotent means
that a transaction can happen once or more than once and still havethe same outcome For example setting the price that gas pumpscharge for gas is an idempotent operation
If all of the component transactions are idempotent, it simplifiesthe task of recovery from a crash, because the only information thatneeds to be saved is the fact that the transaction was begun It is notnecessary to be certain that composite transaction has not completed.The other area you will need to address is isolation Though eachcomponent transaction has the isolation property, this sequence of events
Trang 16If the composite transaction is isolated from other transactions,then neither transaction should be able to observe state changes made
by the other This is not the case in the preceding scenario In this
sequence of events, the first half of transaction 1 sees things as they were before transaction 2; the second half of transaction 1 sees things
as they are after transaction 2 If you need only to isolate these actions from each other, you can solve the crash recovery problem and the isolation problem the same way: Before the composite trans-action invokes any of its component transactions, it can store the
trans-transaction data in a file When the trans-transaction is done, it deletes
the file If a crash prevents the completion of the transaction, then when the program restarts it can detect the existence of the file andrestart the transaction
The existence of the file can also be used to isolate transactions ing the Lock File pattern, if the file exists when a composite transactionstarts, it waits until the file no longer exists before it continues
Us-Figure 4.5 is a class diagram that shows your design
Figure 4.5 adds a detail not previously discussed Instead of using just one transaction file, it uses one transaction file per store This is based on an assumption that each store enters data only for itself and for no other stores This means concurrent transactions from differentstores are isolated from each other simply because they are from dif-ferent stores You need the file only to isolate concurrent transactions from the same store Forcing transactions for one store to wait for
FIGURE 4.5 Composite data entry transaction
CompositeTransaction DataEntryDialog
Trang 17transactions for another store to complete introduces an unnecessarydelay.
In this example, it was possible to find a solution that did not requirethat the composite transaction was atomic and isolated This is the excep-tion rather than the rule In most cases, it is necessary to take measures toensure that a composite transaction has all of the ACID properties
FORCES
⁄ Building complex transactions with predictable outcomes from pler transactions is greatly facilitated if the simpler transactions havethe ACID properties
sim-⁄ If the ACID properties of a set of transactions are implemented using
a single mechanism that supports nested transactions, then menting the ACID properties for a composite transaction composed
imple-of those transactions is very easy
Ÿ If the ACID properties of a set of component transactions are mented using a mechanism that does not support nested transac-tions, then implementing ACID properties for a compositetransaction is more difficult Implementing a composite transactionwith component transactions whose ACID properties are imple-mented using incompatible mechanisms that do not work with eachother is also difficult In some cases, it is impossible
imple-Ÿ It is difficult for a maintenance programmer who must maintain acomposite transaction to understand the full inner workings of acomposite transaction, especially if there are multiple levels of com-position
SOLUTION
Design classes that implement complex transactions so that they delegate
as much work as possible to classes that implement simpler transactions.When selecting classes that implement transactions for incorporation intomore complex transactions, you should use classes that already exist andare known to be correct, or you should select classes that will have multi-ple uses
The simpler transactions should have the ACID properties Thatgreatly simplifies the task of ensuring predicable properties for the com-posite transaction
Carefully choose the granularity of the simpler transactions Whendesigning with existing transactions, you generally have to work with the transactions as they exist If you are designing the simpler transactions
58 ■ CHAPTER FOUR
Trang 18along with the complex, the granularity of the simpler transaction should
be a balance between the need to keep the simpler transactions simple andthe need to keep the more complex transactions understandable
Sometimes, circumstances make it complicated to ensure the ACIDproperties of a composite transaction Figure 4.6 shows the structure of asimple composite transaction design when there are no such complicatingcircumstances
FIGURE 4.6 Composite transaction pattern
TransactionManager
Uses 1
«interface»
TransactionParticipantIF startTransaction( ) commit( ) abort( )
CompositeTransactionLogic startTransaction( )
commit( ) abort( ) transactionOperation1( ) transactionOperation2( )
commit( ) abort( )
transactions- for 1
1
1 1
1
Manages- for
Trang 19The classes shown in Figure 4.6 play the following roles in theComposite Transaction pattern:
CompositeTransactionLogic. Although there are many ways toorganize the logic of a transaction, the most common design is
to have one class that encapsulates the core logic of a tion This class can encapsulate the core logic for multiplerelated transactions
transac-ComponentTransaction1, ComponentTransaction2, Classes
in this role encapsulate a component transaction that is part ofthe composite transaction CompositeTransactionLogic
classes delegate transaction operations directly to
ComponentTransactionobjects However, transaction ment operations that begin or end a transaction are delegatedindirectly through a TransactionManagerclass
manage-TransactionManager. This class encapsulates reusable commonlogic to support atomicity and isolation For distributed transac-tions, it may also encapsulate the logic to support durability
CompositeTransactionLogicobjects use an instance of thisclass to manage a transaction
In order to be independent of the classes that it manageswithin a transaction, it interacts with these classes through a
TransactionParticipantIFinterface
TransactionParticipantIF. TransactionManagerclasses interactwithComponentTransactionclasses through an interface inthis role
ComponentTransactionAdapter. UnlessComponentTransaction
classes are specifically designed to work with the
TransactionManagerclass being used, they don’t implementtheTransactionParticipantIFinterface that the
TransactionManagerclass requires Classes in the
ComponentTransactionAdapterrole are adapters that ment the TransactionParticipantIFinterface with logic thatdelegates to a ComponentTransactionclass and supplementsits logic in whatever way is necessary
imple-There are two areas in which applications of this pattern most oftenvary from the organization shown in Figure 4.5 Both areas of variationusually add complexity
The first area of variation is that some portions of the compositetransaction’s logic may not already be encapsulated as a self-containedtransaction In many cases, such logic is too specialized for you to have an
60 ■ CHAPTER FOUR
TE AM
FL Y
Trang 20expectation of reusing it It may not be possible to justify encapsulatingsuch specialized logic in this way In these situations, the design usuallylooks like a hybrid of Figures 4.3 and 4.6, with some portions of the logicencapsulated in self-contained transactions and the unencapsulated por-tions having the additional details shown in Figure 4.3.
The other area of variation is managing the predictability of the posite transaction’s outcome The preferred strategy for doing that is toensure that the composite transaction has the ACID properties Extensiveexperience has shown this is to be a successful strategy Though usingcomponent transactions that have the ACID properties may simplify thetask of ensuring that the composite transaction has the ACID properties, it
com-is not sufficient
The simplest situation for ensuring the ACID properties of the posite transaction is when there is a single mechanism for ensuring theACID properties of all of the component transactions and the mechanismsupports nested transactions Such a mechanism does not only allow indi-vidual component transactions to abort themselves, it also allows the com-posite transaction to abort and restore all objects modified by committedcomponent transactions to the state they had at the beginning of the com-posite transaction
com-The simplest possibility is that you are using a tool to manage actions and the tool supports nested transactions Alternatively, if you con-trol the implementation of all of the component transaction classes thatyou are using, then it is relatively easy to modify the techniques described
trans-by the ACID Transaction pattern to support nested transactions
If the component transactions are managed by a mechanism thatdoes not support nested transactions, then you will need a different way toensure the predictable outcomes of the composite transactions If the com-ponent transactions are managed by different mechanisms, as is the case
in the example under the “Context” heading, it is also necessary to find adifferent way to ensure the predictability of the outcome of the compositetransaction
The Two Phase Commit pattern describes a way to combine ponent transactions that have the ACID properties and are managed bydifferent mechanisms into one composite transaction that has the ACIDproperties However, you may not be able to use the Two Phase Commitpattern if all of the classes that encapsulate the component transactionshave not been designed to participate in the Two Phase Commit pat-tern
com-In some cases, it may be impractical or even impossible to ensure theACID properties for the composite transaction You will find descriptions
of common alternatives and how to implement them under the
“Implementation” heading
Trang 21Ÿ If you are not able to use nested transactions or the Two PhaseCommit pattern to manage the ACID properties of a composite trans-action, it may be difficult to implement the ACID properties for thecomposite transaction It may even be impossible to implement theACID properties for the composite transaction In such situations,you are forced to compromise on the guarantees you can make aboutthe predictability of the transaction’s outcomes.
•If there are no dependencies between component transactions, then it
is possible for them to execute concurrently
IMPLEMENTATION
There are a number of lesser guarantees that you may try to implementwhen it is not possible to enforce the ACID properties for a compositetransaction Some of the more common ones are discussed in this section
When it is not possible to ensure that a transaction is atomic, it may
be possible to ensure that it is idempotent If you rely on idempotencerather than atomicity, then you must be able to ensure that a transactionwill be completed at least once after it is begun
In some situations, it is possible to ignore the issue of isolation If thenature of the transaction ensures that no concurrent transactions willmodify the same objects, then you do not need to anything to ensure thatthe transactions execute in isolation
KNOWN USES
Sybase RDBMS and SQL Server support nested transactions and facilitatethe construction of composite transactions
62 ■ CHAPTER FOUR
Trang 22JAVA API USAGE
The Java Transaction API has facilities to aid in the construction of posite transactions
Command. The Command Pattern (described in Volume 1) can be
the basis for an undo mechanism used to undo operations andrestore objects to the state they were in at the beginning of atransaction
Composed Method. The Composed Method pattern (described in
Volume 2) is a coding pattern that describes a way of composing
methods from other methods and is structurally similar to theway the Composite Transaction pattern composes transactions
Lock File. The Lock File pattern can be used to enforce the tion property for a composite transaction
isola-Two Phase Commit. The Two Phase Commit pattern can be used
to ensure the ACID properties of a composite transaction posed from simpler ACID transactions
com-Mailbox. When there is a need to ensure the reliability a compositetransaction, you will want to take steps to ensure the reliability ofthe component transactions that constitute it If the compositetransaction is distributed, you will also want to ensure the reli-able transmission of messages between the objects that partici-pate in the transaction by such means as the Mailbox pattern
Trang 24This pattern is based on material that appears in [Gray-Reuter93].
SYNOPSIS
If a transaction is composed of simpler transactions distributed acrossmultiple database managers, you want them to either all complete success-fully or all abort, leaving all objects as they were before the transactions.You achieve this by making an object responsible for coordinating thetransactions so that they all complete successfully or all abort
CONTEXT
Suppose that you have developed software for a barter exchange business.The software is responsible for managing barter exchanges It recordsoffers of exchange, acceptances, and the consummation of each exchange.The business has grown to the point where it has offices in a number
of cities, each office facilitating barter exchanges among people local to itscity The business’s management has decided that it is time to take the busi-ness to the next level and allow barter between people in different parts ofthe country They want someone in one city to be able to swap theater tick-ets for balloon rides near a different city Currently, that is not possible.Each office runs its own computer that manages transactions for itsclients The offices run independently of each other In order to supportexchanges between clients of different offices, it must be possible to exe-cute ACID transactions that are distributed between multiple offices
To make this happen, there must be a mechanism that coordinates theportion of each transaction that executes in each office It must be the casethat every portion of each transaction successfully commits or every portion
of each transaction aborts It must never happen that one office thinks that atransaction completed successfully and another thinks that it aborted
Trang 25⁄ If any one of the component transactions participating in a ite atomic transaction fails, all must fail This implies that the compo-nent transactions are coordinated in some way.
compos-⁄ Though it is possible to distribute the responsibility for coordinatingtransactions over multiple objects, it is an unusual design decision.Distributing coordination of self-contained transactions adds com-plexity It is an area that is not as well understood as designs thatmake a single object responsible for the coordination Distributedcoordination of transactions remains a valid research topic
⁄ There is extensive industry experience with designs that make a single object responsible for coordinating transactions Because ofthis experience, designs that make a single object responsible forcoordinating transactions are well understood and widely writtenabout
⁄ The results of a transaction should persist as long as any objects may
be interested in the results or until another transaction changes thestate of the affected objects If the transactions being coordinatedhave the ACID properties, then their durability attribute implies thatthis will be true for the results of each of the coordinated transactionsindividually
Ÿ The responsibility for coordinating component transactions persistsuntil the composite transaction has completed However, the object(s)responsible for coordinating a transaction may experience a cata-strophic failure during a transaction
Ÿ The requirements for some applications imply that some compositetransactions should not be atomic This is often true for long-livedtransactions or application-level transactions For example, an inven-tory application for a chain of retail stores may support a transaction
to order additional merchandise from a warehouse If the warehousedoes not have all of the ordered merchandise, then the warehousewill need to backorder the merchandise and send the merchandise tothe stores after the merchandise arrives To allow store managers toeffectively manage the display of their inventory, they will need to beaware of the status of orders they send to the warehouse It must bepossible for store managers to know when the merchandise that theyordered is backordered
SOLUTION
Make a single object responsible for coordinating otherwise-independentACID transactions participating in a composite transaction so that thecomposite transaction has the ACID properties The object responsible for
the coordination is called the coordinator The coordinator coordinates the
66 ■ CHAPTER FOUR