|
Title | Applying COM+ |
Authors | Gregory Brill |
Publisher | New Riders |
Published | October 2000 |
ISBN | 0735709785 |
Price | US 49.99 |
Pages | 450 |
|
Chapter 9 - Compensating Resource Managers
Frustrated
with the inadequacies of
the prevailing hierarchical storage schemes of the day, in 1970 an IBM
researcher named Edgar Codd published a general specification for a relational
database (Codd, Edgar. 1970. A Relational Model of Data for Large Shared Data
Banks. Communications
of the ACM, Vol. 13,
No. 6, June, 377-387.). Codd did not say much about how his relational calculus
might be implemented in a server, but he fired the starting gun for a new era
in data storage technology.
Companies like Oracle, Ingres,
and of course IBM rushed to provide actual, scalable implementations of Codd�s work. After a period of
contention about the query language (Oracle and others favored SQL�Ingres held
out for something called QUEL), relational database technology became, more or
less, industry standard.
Relational database systems
have grown significantly in terms of their capabilities. Far from simply being
efficient, convenient repositories for data, they have also become safe, robust
places to put data. Although each Database
Management System (DBMS) handles transactions and disaster recovery
differently, they all promise a high degree of fault-tolerance.
For all that relational
databases offer, however, there are simply times when you need to manipulate
data that just doesn�t fit well into a relational model. And although it is a
relatively straightforward process to write code to query from and modify data
in some proprietary form, it is exponentially more complex to make the process
fail-safe.
Fortunately, COM+ provides a
framework for writing server-side components that can modify data at the behest
of a transactional client (and be governed by the Distributed Transaction
Coordinator [DTC]), just as Microsoft SQL Server does. This framework is called
the Compensating
Resource Manager (CRM) and is the subject
of this chapter.
The Resource Manager
Reinventing the code necessary to handle power-outages,
user-requested rollbacks, interrupted modifications, and so on for
non-relational data is not something most developers have the time and
resources to do. Fortunately, COM+ provides an architecture that helps�that of
the CRM.
Before we talk about the CRM, we need to introduce some new
terminology. Microsoft�s technology can sometimes be confusing because they
often attach new names to existing technologies. For example, the terms OCX, ActiveX,
OLE, and COM can all be used to describe the
exact same technology.
Usually, Microsoft reinvents a
technology when it is refitted to play a part in some new, larger initiative.
For example, the term OCX was once
used to describe a graphical COM control that could be put on a VB form. When
Microsoft took on Java on the Web-front, however, it adapted the concept of the
OCX so that it could exist on an Internet Explorer 3 Web page. At this point,
the humble OCX became an ActiveX control even though it didn�t fundamentally
change. The term ActiveX became a more
general term that described Microsoft�s overall Web strategy in a more generic
fashion. According to the party line, the ActiveX control was a particular
implementation and utilization of ActiveX technology.
Just as Microsoft�s sudden
emphasis on the World Wide Web warped established terminology and made strange
bedfellows of previously unrelated or loosely related technologies, now COM+�s
emphasis on the Enterprise is doing the same thing. In the new Enterprise
philosophy, the term database (or worse, relational database) is thought to be
too limiting. These words evoke images of SQL and relational tables, but not
all corporate data repositories are based on relational databases. And so,
Microsoft has adopted a term already used in the industry that can refer to any
type of application that manages data and can
participate in COM+ transactions�Resource Manager (RM).
Strictly speaking, an RM must
support COM+ transactions, but this is really to say that it can talk to the DTC
and knows how to handle a two-phased commit. Specifically, an RM must support
one or both of the following two-phased commit protocols:
- X/Open XA.
X/Open (www.opengroup.com) is an international standards body that defined
the XA standard. XA is not an acronym, rather it is the name of a protocol.
Specifically, it is a two-phased commit protocol originating in the UNIX world.
Most popular transaction monitors (TMs) systems�BEA Tuxedo and Encina, to name
a couple�interoperate with a variety of relational databases such as Oracle,
DB2, and Sybase (all are examples of RMs) to make distributed transactions
possible. UNIX-style TMs perform the same functionality as Microsoft�s
DTC, and XA is the protocol that the TMs use to govern one or many XA-compliant
RMs during a distributed transaction.
- OLE
Transactions. This protocol is a two-phased commit
protocol just like XA. OLE Transactions is based on methods callable from COM
interfaces; XA, on the other hand, is API-based. In the end, however, both
protocols accomplish the same result. OLE Transactions can be said to be DTC�s
native two-phase protocol. For the DTC (and therefore COM+ transactions) to
work with an RM, the RM must support OLE Transactions or must supply an OLE
Transaction-to-XA conversion mechanism. As discussed in "XA and the DTC,"
sidebar, this conversion mechanism is usually located in the ODBC driver or
OLE-DB provider supplied by the vendor.
Most major relational databases support one or both of these
protocols. If they support OLE Transactions, they can automatically participate
in COM+ transactions. If they only support XA, they can only participate in
COM+ transactions if the ODBC driver (or some other form of resource dispenser
the vendor makes available) works with the DTC to translate the OLE Transaction
calls to XA-compliant ones. If the RM supports both, it gets the best of all
worlds.
So, RMs are commonly relational
databases, but do not need to be. Microsoft Message Queue (MSMQ) is an example of an
RM that is not a relational database. As you will see, COM+ messaging relies on
MSMQ. It gives you the ability to send asynchronous transactional messages.
We�ll discuss this more in Chapter 10, "Queued Components."
XA and the DTC
XA is not natively handled by
the DTC. The DTC uses the OLE Transactions protocol (described in the next
section, "Components of the CRM"). It is important to realize that although the
DTC has facilities (a series of conversion interfaces) to convert an OLE-based
transaction to an XA-based one, this is not an automatic process. The DTC
cannot, on its own, interoperate with an XA-based RM like an older version of
Oracle. However, if the ODBC driver (or other connection resource dispenser)
supplied by the RM�s vendor is specifically designed to work with the DTC to
perform this conversion behind the scenes, it may seem as though the DTC does
automatically govern transactions on XA-only RMs. Don�t be fooled�the DTC is
biased toward OLE Transactions. To work with XA-only RMs, the vendor has to
supply a translator�usually buried in its ODBC driver or OLE-DB Provider.
By supporting either XA or OLE Transactions, an RM is indicating it
knows how to temporarily buffer changes to its data source, whatever that may
be, and roll them back or commit them whenever asked by the DTC. This implies
that the RM needs to keep some kind of log that records all changes. If the RM
crashes, it can reopen the log when it is run again and either complete or undo
whatever changes are necessary to ensure the integrity of its data.
Furthermore, the RM must serialize all database changes while in
the midst of a transaction. It would be very difficult to roll back changes
indicated in the log, if other processes were continually allowed to change
data underneath it. Most relational databases lock the table(s) or row(s) when
a transaction is in progress and block all data modifications for all
connections other than those enlisted in the transaction. The locks are
released only after the transaction is complete.
In short, writing a proper RM is
difficult. The CRM is a kind of template and protocol that, if followed, takes
a lot of the complexity out of writing an RM.
Components of the CRM
You have data that is non-relational in nature and have decided to
write your own CRM to manage this data on behalf of transactional COM+ objects.
Although writing a true RM poses quite a challenge, writing a CRM is not
difficult.
A CRM consists of two separate COM coclasses that you must
implement. The first coclass is the Worker
.
The second coclass is the Compensator
.
Worker
The Worker
coclass
supports whatever methods you choose to give it. In our example (Listing 9.1),
we have a Worker
class with methods like AddAccount()
, ChangeAccount()
,
and so on. In the transactional examples we�ve discussed so far, COM+ objects
are manipulating relational databases (which are RMs) via ADO methods like Execute()
.
Philosophically, an object that calls ChangeAccount()
on our Worker
object is doing the exact same thing as an object
executing an update statement via some method of ADO; it is requesting that a
data change be made by an underlying RM in the context of a transaction.
Listing 9.1 Constants
Common to Both Worker and Compensator
Public Const XACT_E_RECOVERYINPROGRESS = &H8004D082
Public Const ADD_ACCOUNT_COMMAND = 1
Public Const
Public Const CHANGE_ACCOUNT_COMMAND = 3
The Worker
knows
about the structure of the underlying data and knows how and where to make
changes. However, and this is the interesting part, although the Worker
coclass might make changes to the underlying data, these changes require the
participation of the Compensator
to make these changes permanent or to undo them
altogether should any COM+ object participating in the transaction call SetAbort()
.
The relationship between the Worker
and Compensator
is
very much like the relationship
between an office worker and his office manager. It is often, though not
necessarily, the case that a worker (Worker
) does most of the legwork, but ultimately it is
the manager (Compensator
) who applies a rubber stamp to make the change
permanent and official. On the other hand, the manager might say, "This work is
no good!" or "One of the higher-ups have cancelled the project, so what you�ve
done is no longer valid" and see to it that all the work is undone. Of course,
different offices have different balances of responsibility between workers and
their managers. In some companies, the clerk might merely propose a thing, and
the manager actually does most the legwork to make it happen. There is no
absolute rule about who does what, except that both the Worker
and Compensator
must work together.
It is important to realize that, just as with our office example, a
Worker
and Compensator
are not necessarily working at the same time. It might be the case that the Worker
works the evening shift and leaves some kind of information log about what he
has done with an office clerk. When the manager (Compensator
) comes in
the next morning, the Clerk
gives the manager the log and says, "This
what the Worker proposed doing, can you approve this and make it
permanent?"
The CRM Clerk
Note that we have introduced
another player in our drama�the Clerk
. The Clerk
keeps log information on behalf of the Worker and
shares it with the Compensator
when the Compensator
is available. In this metaphor, there are three
roles or characters�that of Worker
, Compensator
, and Clerk
. I have chosen these characters because they
have direct parallels when writing a real CRM.
The Worker
object must support interfaces and
methods that transactional COM+ objects can call to make modifications to your
data. In a very real sense, the Worker
is your RM, so these methods can be whatever you want them to be and can do
whatever you deem appropriate. Whatever these methods do, however, it is
critical that the Worker
record any actions resulting from each
method call to some kind of log before the Worker
makes any kind of physical change. Remember, just as
with our office example, there is a clerk who records a log of what the Worker
intends to do and passes it from Worker
to Compensator
. It is
ultimately the Compensator
who either approves the changes and
makes them permanent or undoes them. If the Worker
fails to record
his actions before doing them and then decides to quit (or crash), the Compensator
has no idea how to undo the Worker�s changes. This is why it is
important that the Worker
log his actions with the Clerk
before making any physical changes. In the CRM, this clerk is called,
appropriately, the CRMClerk.
The CRMClerk is a COM object defined in the comsvcs.dll type
library that all Workers must ask for and use. It creates a log
on behalf of the Worker
; after the Worker
obtains an
instance of the CRMClerk
, it can call the Clerk�s
methods to make additions to this log. Getting the Clerk
is as simple as
using the following code:
Dim Clerk as CRMClerk
Set Clerk = New CRMClerk
The Clerk
allows the Worker
to write to a
log file (created and maintained by Clerk
) by supporting the ICrmLogControl
interface. The Worker
need only QueryInterface (QI)
for it, as demonstrated:
Dim CrmLogControl As ICrmLogControl
Set CrmLogControl = Clerk
Note that in VB, the ICrmLogControl
interface is the CRMClerk�s
default interface. You can either call ICrmLogControl�s methods through a
variable of type ICrmLogControl
or directly on the Clerk
object itself.
After the Worker
has the ICrmLogControl
interface, it can write to the Clerk�s log file. The Worker
can either write an array of variants or a binary large object (BLOB). Let�s
demonstrate using variants (see Listing 9.2).
Listing 9.2 Writing
an Array of Variants to the Log File
Dim vntLogFileEntries(2) As Variant
vntLogFileEntries(0) = "LEDGERID:66:MAKEBALANCE:4500"
vntLogFileEntries(1) = "LEDGERID:176:MAKEBALANCE:5500"
vntLogFileEntries(2) = "WRITEEVENT: Debit and Credit for $500 complete"
On Error GoTo ErrorHandler
It really is as simple as it seems. The Worker
writes
arrays of variants via the CRMClerk
to the log file the CRMClerk
will maintain (a complete CRM Worker is provided in Listing 9.3). It is
completely up to the developer to decide how much and what kind of data should
be logged. Ultimately, the log file is presented to the Compensator
in
one of the following two ways:
- If all COM+ objects participating in the
transaction vote that the transaction is good, the
Compensator
is told to
commit the changes and is given the log.
- If one or more objects vote for the
transaction to fail, however, the
Compensator
is asked to roll back the
changes and once again, is given the log to do so.
Whatever logging protocol you create and use, it must be understood
by both the Worker
and Compensator
. What�s more, the log should
contain all the information that the Compensator
could possibly need to either
commit or roll back the changes.
Let�s take a moment and summarize what we have so far. We know that
the Worker
writes data changes to a log file, and we know that that log file will be given
to the Compensator
and asked by COM+ to either commit or roll back the changes. This question,
however, arises: Who does the physical work of modifying the data, the Worker
or the Compensator
?
Writing to a log file is one thing, and it is clear that the Worker
writes to the log file via the CRMClerk
, and the Compensator is
given the log when the COM+ transaction as a whole commits or aborts. When it
comes to actual, physical changes, the developer determines how much actual
work the Worker
and Compensator
each perform. It is really
governed by what kind of data the CRM is supposed to modify and how many steps
are necessary to modify it.
The first example most
developers see of a CRM is one that creates and modifies flat files. In this CRM,
the Worker
creates the file in a temporary directory after writing the file�s
path in the log. The Compensator
then receives the log and from it, figures out
where the temporary file is. If the Compensator
is called in the context of a commit, it copies
the file to the permanent directory. If it is called because a participating
object named SetAbort()
, it deletes the file from the temporary
directory. Without getting too complex but adding a little spice, Listing 9.3
displays the code for a CRM Worker
that performs modifications to an XML file
using Microsoft�s XML Document Object Model (DOM). If the listing seems
daunting, just read the source comments along the way and you�ll find that
there is nothing at all complicated about a CRM Worker.
Listing 9.3 A
Complete CRM Worker Component that Modifies Account Balance in an XML File
Option Explicit
Dim Clerk As CRMClerk
Dim CrmLogControl As ICrmLogControl
Const COMPENSATOR_ProgID = "XMLCRMVB.XMLCompensatorVB"
Const COMPENSATOR_DESCRIPTION = "XML VB Compensator component"
Dim bIsCompensatorRegistered As Boolean
Dim vntLogFileEntries(3) As Variant
Dim xmlDoc As New DOMDocument
Dim xmlNodes As IXMLDOMNodeList
Dim xmlNode As IXMLDOMNode
Dim xmlBalanceNode As IXMLDOMNode
Dim xmlAccountNode As IXMLDOMNode
Dim xmlAccountBalanceNode As IXMLDOMNode
Dim xmlAccountNumberNode As IXMLDOMNode
Dim xmlRootNode As IXMLDOMNode
Private Sub Class_Initialize()
bIsCompensatorRegistered = 0
End Sub
Private Sub ObtainCRMClerk()
On Error GoTo ErrorHandler
Set Clerk = New CRMClerk
Set CrmLogControl = Clerk
Exit Sub
ErrorHandler:
MsgBox "ObtainCRMClerk ErrorHandler " + Err.Description
End Sub
Private Sub RegisterCompensator()
If bIsCompensatorRegistered = True Then
Exit Sub
End If
On Error Resume Next
Do
CrmLogControl.RegisterCompensator COMPENSATOR_ProgID, �COMPENSATOR_DESCRIPTION, CRMREGFLAG_ALLPHASES
DoEvents
Loop Until Err.Number < > XACT_E_RECOVERYINPROGRESS
If Err.Number <> 0 Then GoTo ErrorHandler
bIsCompensatorRegistered = True
Exit Sub
ErrorHandler:
MsgBox "XMLVBWorker: RegisterCompensator" & Err.Description & " - " & ; ; ; ;�Hex$(Err.Number)
End Sub
Sub AddAccount(AccountNumber As Long, Balance As Double, Filename As String)
Dim dAccountBalance As Double
Dim iNode As Integer
On Error GoTo ErrorHandler
Initialize
If GetBalance(AccountNumber, Filename, dAccountBalance) Then
MsgBox "Account already exists"
GetObjectContext.SetAbort
Exit Sub
End If
vntLogFileEntries(0) = ADD_ACCOUNT_COMMAND
vntLogFileEntries(1) = AccountNumber
vntLogFileEntries(2) = Balance
vntLogFileEntries(3) = Filename
CrmLogControl.WriteLogRecordVariants vntLogFileEntries
CrmLogControl.ForceLog
xmlDoc.Load Filename
Set xmlNodes = xmlDoc.childNodes.Item(0).childNodes
For iNode = 0 To xmlNodes.length - 1
Set xmlNode = xmlNodes.Item(iNode).childNodes.Item(0)
If xmlNode.Text = AccountNumber Then
GetObjectContext.SetAbort
Exit Sub
End If
Next
Set xmlRootNode = xmlDoc.documentElement
Set xmlAccountNode = xmlDoc.createElement("ACCOUNT")
Set xmlAccountNumberNode = xmlDoc.createElement("ACCOUNTNUMBER")
Set xmlAccountBalanceNode = xmlDoc.createElement("BALANCE")
xmlAccountBalanceNode.Text = Balance
xmlAccountNumberNode.Text = AccountNumber
xmlAccountNode.appendChild xmlAccountNumberNode
xmlAccountNode.appendChild xmlAccountBalanceNode
xmlRootNode.appendChild xmlAccountNode
xmlDoc.save Filename
GetObjectContext.SetComplete
Exit Sub
ErrorHandler:
MsgBox "An error occured in ADD " & Err.Description
End Sub
Sub RemoveAccount(AccountNumber As Long, Filename As String)
Dim dBalanceBefore As Double
Dim iNode As Integer
On Error GoTo RemoveAccountProblem
Initialize
If Not GetBalance(AccountNumber, Filename, dBalanceBefore) Then
MsgBox "Couldn�t get balance!"
GetObjectContext.SetAbort
Exit Sub
End If
vntLogFileEntries(0) =
vntLogFileEntries(1) = AccountNumber
vntLogFileEntries(2) = dBalanceBefore
vntLogFileEntries(3) = Filename
CrmLogControl.WriteLogRecordVariants vntLogFileEntries
CrmLogControl.ForceLog
xmlDoc.Load Filename
Set xmlNodes = xmlDoc.childNodes.Item(0).childNodes
For iNode = 0 To xmlNodes.length - 1
Set xmlNode = xmlNodes.Item(iNode).childNodes.Item(0)
If xmlNode.Text = AccountNumber Then
Exit For
End If
Next
If xmlNode.Text < > AccountNumber Then
GetObjectContext.SetAbort
Exit Sub
End If
xmlDoc.childNodes.Item(0).removeChild xmlNodes.Item(iNode)
xmlDoc.save Filename
GetObjectContext.SetComplete
Exit Sub
RemoveAccountProblem:
MsgBox "Account was NOT removed!"
End Sub
Sub CreditAccount(AccountNumber As Long, Amount As Double, Filename As String)
ChangeAccount AccountNumber, Amount, 1, Filename
End Sub
Sub DebitAccount(AccountNumber As Long, Amount As Double, Filename As String)
ChangeAccount AccountNumber, Amount, 0, Filename
End Sub
Private Sub ChangeAccount(AccountNumber As Long, Amount As Double, creditOrDebit �As Boolean, Filename As String)
Dim dBalanceBefore As Double
On Error GoTo ChangeAccountProblem
Dim iNode As Integer
Initialize
If Not GetBalance(AccountNumber, Filename, dBalanceBefore) Then
GetObjectContext.SetAbort
Exit Sub
End If
vntLogFileEntries(0) = CHANGE_ACCOUNT_COMMAND
vntLogFileEntries(1) = AccountNumber
vntLogFileEntries(2) = dBalanceBefore
vntLogFileEntries(3) = Filename
CrmLogControl.WriteLogRecordVariants vntLogFileEntries
CrmLogControl.ForceLog
On Error GoTo ChangeAccountProblem
xmlDoc.Load Filename
Set xmlNodes = xmlDoc.childNodes.Item(0).childNodes
For iNode = 0 To xmlNodes.length - 1
Set xmlNode = xmlNodes.Item(iNode).childNodes.Item(0)
If xmlNode.Text = AccountNumber Then
Exit For
End If
Next
If xmlNode.Text <> AccountNumber Then
GetObjectContext.SetAbort
Exit Sub
End If
Set xmlBalanceNode = xmlNodes.Item(iNode).childNodes.Item(1)
If creditOrDebit Then
xmlBalanceNode.Text = xmlBalanceNode.Text + Amount
Else
xmlBalanceNode.Text = xmlBalanceNode.Text - Amount
End If
xmlDoc.save Filename
GetObjectContext.SetComplete
Exit Sub
ChangeAccountProblem:
GetObjectContext.SetAbort
End Sub
Private Function GetBalance(AccountNumber As Long, Filename, ByRef Balance As �Double) As Boolean
Dim iNode As Integer
On Error GoTo GetBalanceProblem
xmlDoc.Load Filename
Set xmlNodes = xmlDoc.childNodes.Item(0).childNodes
For iNode = 0 To xmlNodes.length - 1
Set xmlNode = xmlNodes.Item(iNode).childNodes.Item(0)
If xmlNode.Text = AccountNumber Then
Exit For
End If
Next
If xmlNode.Text <> AccountNumber Then
GetBalance = False
Exit Function
End If
Set xmlBalanceNode = xmlNodes.Item(iNode).childNodes.Item(1)
Balance = xmlBalanceNode.Text
GetBalance = True
Exit Function
GetBalanceProblem:
GetBalance = False
End Function
Private Sub Initialize()
If CrmLogControl Is Nothing Then
ObtainCRMClerk
End If
Call RegisterCompensator
End Sub
Compensator
The Compensator�s job is to either commit the
changes initiated by the Worker
or roll them back. COM+ notifies the Compensator
of which action to perform, commit or roll back, and presents the Compensator
with each and every entry in the log, one at a time. Interestingly, if the Compensator
is asked to commit, it is given each log entry in the same order as it was
written by the Worker. If, on the other hand, the Compensator
is asked to roll back, it is given each log entry in reverse order. We look at
this process in the next couple paragraphs.
COM+ objects call on the Worker, but COM+ itself calls on the services of the
Compensator
.
A given Compensator
is associated with a particular Worker
by virtue of the Worker
executing the following code early in its
life:
Clerk.RegisterCompensator "MyCompensator.Comp", "[put your description here]", �CRMREGFLAG_ALLPHASES
The last argument is a flag that indicates which phases the Compensator
wants to be involved in. It can choose to be called during the Commit
phase and
not in the Prepare
phase, if there is nothing it needs to do in the latter and
thus wants to improve performance. For now, we�re going to assume that our Compensator
wants to be involved in all phases as this is the norm. For completeness,
however, a description of each registration flag can be found in the subsequent
section, "Phase I: Prepare."
Note that although a Worker
explicitly associates itself with
a particular class of Compensator
, they are not necessarily running at
the same time. And under no circumstances does the Worker
call methods on
the Compensator
,
or vice versa. Their only form of communication, by design, is the log. Upon
instantiating the Compensator
, COM+ gives it an ICrmLogControl
interface however, the Compensator
cannot use this interface to read
the log, only to write to it. To read the changes made by the Worker
,
a Compensator
must implement one of the following two interfaces:
- ICrmCompensator
- ICrmCompensatorVariants
COM+ automatically QIs the Compensator
for one of the above
interfaces and uses its methods to "feed" the Worker�s changes to the Compensator
.
The particular inter-face your Compensator
should implement depends on
the type of data it works with. Specifically, if the Compensator
is
dealing with binary data, it implements the ICrmCompensator
if it
is dealing with data that can confidently be represented by variants, it uses ICrmCompensatorVariants
.
In either case, COM+ calls methods on these interfaces to inform the Compensator
what the specific changes are (that is, what the Worker
wrote to the
log) and whether it should commit them or roll them back.
For simplicity, let�s assume that variants are sufficient to
represent our data modifications in the log. In this case, our Compensator
only needs to implement the ICrmCompensatorVariants
interface.
The complete source code for
our XML Compensator
can be found under the section, "The Complete Compensator," found toward the end of this chapter. I am not
going to show its source code here because the Compensator has a more complicated job than the Worker, and
we need to lay a little more groundwork before its implementation will make
perfect sense. Remember that COM+ transactions occur in two distinct phases�a
Prepare
phase and a Commit
phase. If all objects participating in a transaction
vote positively, your CRM is asked to commit. For now, let�s assume this is the
case�all the objects have voted positively and COM+ wants to commit the
transaction. After instantiating the Compensator
, COM+ QIs for its ICrmCompensatorVariants interface. It then calls the methods of this
interface to walk your CRM through the Prepare
and Commit
phases. I�ll talk
about the former case first.
Phase I: Prepare
This phase involves three method calls, described in section "Step
1: General Prepare." Before I discuss them fully, note that it is only possible
for a CRM to abort a transaction during this phase. One of the methods of the CRMClerk�s
default interface, ICrmLogControl
, is ForceTransactionToAbort()
.
Because the Compensator
is given this interface on instantiation (COM+
calls its SetLogControlVariants
method), you might think that the Compensator
can call this method to abort the transaction it doesn�t work. This method is
for the Worker, and as you see, the only way a Compensator
can abort a transaction during the Prepare
phase is by returning FALSE
when COM+ calls its final preparation method.
The Prepare
phase is crucially important, because after a Compensator
indicates it has successfully completed this phase, the CRM can no longer abort
the transaction. COM+ expects the Compensator to be able to commit the
changes, no matter what else might happen.
Although the Prepare
phase is important, it is allowable for a Compensator
to opt out of this phase altogether. This is not to say that the Prepare phase
doesn�t happen; it is an inherent part of Object Linking and Embedding (OLE)
Transactions and always occurs when COM+ attempts to commit a transaction. The Compensator
does, however, have the option not to be notified of this phase or to participate
in it. It does this by specifying one or a combination of five enumerated
values in the third argument to ICrmLogControl�s RegisterCompensator()
method, as follows:
Clerk.RegisterCompensator "MyCompensator.Comp", "[put your description here]",
WhatToParticipateIn
WhatToParticipateIn
can be one of the following:
CRMREGFLAG_ALLPHASES
CRMREGFLAG_PREPAREPHASE
CRMREGFLAG_COMMITPHASE
CRMREGFLAG_ABORTPHASE
CRMREGFLAG_FAILIFINDOUBTSREMAIN
These flags are made to be bit-wise OR�d, meaning they can be
combined where their effects are additive. CRMREGFLAG_ALLPHASES
is just a combination of all the flags except for CRMREGFLAG_FAILIFINDOUBTSREMAIN
.
We will talk about this last flag in the subsequent section "When In Doubt,"
but the other flags are pretty straightforward. The word PHASE
does not refer to the two phases of OLE Transactions; they refer to the various
phases of interaction between the CRM and COM+.
There are no absolute rules as
to what CRM phases your Compensator
should participate in; only the developer can
make this judgment (the Compensator
example in Listing 9.4 participates in all of
them). They are included to provide the Compensator
developer with a means of improving performance
by eliminating unnecessary method calls. And, in case you might be thinking
it�no, you cannot register different Compensators
to handle different phases for the same Worker.
Keep in mind that by not
participating in a phase (particularly the Prepare
phase), your Compensator
is implicitly indicating its approval and is saying, in effect, "I wouldn�t do
anything if you did call me during this phase, and I would have given you the
okay anyway. So, don�t bother me, and proceed as if I said everything was
fine."
Assuming that the Worker
asked the Compensator
to
be involved in all phases(CRMREGFLAG_ALLPHASES
), COM+ undertakes the
following steps and calls the following methods during the Prepare
phase of a
commit.
Step 1: General Prepare
BeginPrepareVariants()
The first step in the two-phase commit involves COM+ calling the
CRM�s BeginPrepareVariants()
method. It takes no arguments, and COM+ makes no assumptions about what the Compensator
will do when this method is called. It is really just a notification. COM+
wants the Compensator
to do whatever it needs to do to prepare to
commit a transaction. Some Compensators
might not do anything;
others might allocate a new block of memory, disk space, and so on.
Step 2: Prepare to Make Changes for Each Log Entry
PrepareRecordVariants( [in] VARIANT * pLogRecord,
[out, retval] VARIANT_BOOL * pbForget
);
This method is called once for
every log entry made in the CRMClerk by the Worker
object. COM+ hands each entry to the
Compensator
,
one at a time. The Compensator
knows when it has received every entry, because
COM+ calls the EndPrepareVariants()
(discussed in the next section) function to
indicate when this is the case.
The first argument contains the current log entry; so if this is
the fifth time COM+ has made this call, this argument contains the fifth log
entry made by the Worker
. Note that one entry can contain one or
many variant values. Basically, I am using the term entry to describe one call
made by the Worker
to WriteLogRecordVariants()
.
The second argument, pbForget, gives the Compensator
the opportunity to remove an item from the log. A Compensator
might want
to remove items from the log that are purely informational and relate only to
the Prepare phase but not the Commit phase. In other words, the Worker
might want to send the Compensator information about something it needs
to prepare for. This information might not, however, be pertinent after the
preparation is complete and the CRM is in the Commit
phase, so the Compensator
has an opportunity to remove it while still in the Prepare
phase. There are no
absolute rules; this is only one possibility. Don�t feel pressured to use this
(or any argument) just because it is there.
It is important to realize that,
like BeginPrepareVariants()
, the EndPrepareVariants()
method is informational. What the
Compensator
does or doesn�t do when this method is called is up to it. COM+ just wants to be
sure that the Compensator
knows what it is in for and makes preparations
for the upcoming commit�whatever those preparations might be. The nature of
such preparations, is dependent on the details of the CRM implementation.
Step 3: Finalize Changes
EndPrepareVariants( [out, retval] VARIANT_BOOL * pbOkToPrepare);
When COM+ calls this method, it is notifying the CRM that it has
received all the log entries and the Prepare
phase is about to complete. The
CRM can return TRUE or FALSE indicating whether it has
successfully completed the Prepare
phase. If you return FALSE (or don�t
return anything until the timeout period), COM+ asks all other RMs also
participating in the transaction (if any) to roll back. It also calls the Abort
methods on your CRM (to be discussed soon) and gives the Compensator
the opportunity to undo any changes that the Worker
might have
made.
Do not tell COM+ that it is okay to prepare unless you really mean
it. By returning TRUE, you are promising COM+ that you are
confident that you can commit the transaction in the future. COM+ reserves the
right to reinstantiate and pester your Compensator
repeatedly until it reports a
successful commit.
Phase II: Commit
The Commit
phase is very similar to the Prepare
, except this is
where the CRM is expected to make the Worker�s changes permanent. It is
important to understand that after your Compensator
has completed its Prepare
phase and moves to the Commit
phase, COM+ expects the Compensator
to
be able to commit and might keep rerunning your Compensator
until it
reports to COM+ that the commit ultimately succeeded. You must also realize that COM+ might use a
different instance of your Compensator
in the Prepare
phase than what it uses in the Commit
phase, so don�t keep any
kind of state in member variables, globals, and so on that you need to span the
two phases.
You might experience a sense of d�j�
vu when you look at these methods because some have the identical form
of their counterpart functions in the Prepare
phase.
Step 1: Prepare to Commit
BeginCommitVariants([in] VARIANT_BOOL * bRecovery, );
COM+ calls this method to notify the CRM that it should prepare to
commit what the Worker
has done.�
The second argument has a value of TRUE if something
catastrophic happens after the Compensator
completes its Prepare
phase successfully and tries unsuccessfully to commit on
a prior occasion. After the CRM completes its Prepare
phase without indicating
an error, it is on the hook to commit the transaction. Although your Compensator
might take special, additional steps to handle the case of a recovery, you are
still obligated to commit. If your Compensator
crashes again in this or another method call during a recovery, COM+ reruns
your Compensator
yet again and again indicates that it is committing in the context of a
recovery. For an in depth look at recovery, forcing transactions to abort, and
other boundary conditions consult the book�s source code on RM.
Step 2: Make Changes for Each Log Entry
CommitRecordsVariants( [in] VARIANT * pLogRecord,
[out, retval] VARIANT_BOOL * pbForget
);
This method has the same form and purpose as its counterpart PrepareRecordVariants()
in the Prepare
phase. COM+ calls this method once for every log record made by
the Worker
.
The Compensator
is to make these changes permanent, and it has the option of removing or
forgetting a record if it is certain that it is complete or deems it no longer
necessary.
Step 3: Commit Changes
EndCommitVariants()
This method notifies the Compensator
that it has received all log
entries. As the Compensator
does not return an error code or
crash, the commitment is complete as far as COM+ is concerned, and the log is
discarded.�
Aborting Transactions
Sadly, we acknowledge that not
every transaction can commit. Even if all COM+ objects vote Yes on the
transaction and COM+ attempts to commit it via the DTC, any participating RM or
CRM can fail. The RM or Compensator
for the CRM can specifically indicate failure in
the Prepare
phase by returning FALSE when EndPrepareVariants()
is called or by crashing. Similarly,
any COM+ object that is manipulating an RM or CRM can call SetAbort()
in
the context of a transaction. In either case, COM+ asks all RMs and CRMs to
roll back their changes.
In the event of a rollback request by the Distributed Transition
Coordinator (DTC), COM+ calls the following methods of the CRM�s ICrmCompensatorVariants
(or ICrmCompensator
if using binary data) interface. However, these methods are only called if the
transaction aborts in the following manners:
- ;A COM+ object calls
SetAbort()
.
- ;During the
Prepare
phase, the CRM crashes or
returns FALSE when COM+ calls its EndPrepareVariants()
method.
- ;Another RM reports failure or fails to check
in during its
Prepare
phase.
- ;Your CRM crashes after its
Prepare
phase but
before it receives a commit command from the DTC. This is called an in-doubt
state, and we talk about it in the section "When In Doubt."
Also note that the CRM abort methods are not called if the CRM
crashes during the Commit
phase. Remember: if the CRM successfully completes
its Prepare phase, aborting the transaction is no longer an option. We talk
about how to handle this case after we take a look at the abortive functions of
ICrmCompensatorVariants.
Step 1: Prepare to Abort
BeginAbortVariants [in] VARIANT_BOOL * recovery)
This method notifies the Compensator
that it should prepare to
abort all changes made by the Worker
.
The recovery argument is TRUE if the Compensator crashes
during or after its Prepare
phase but before it receives a commit command from
the DTC. If it previously aborted gracefully via ForceTransactionToAbort()
,
or the transaction was voted down by a COM+ object, this argument is FALSE.
Step 2: Undo Each Log Entry
AbortRecordVariants ([in] VARIENT * pLogRecord,
[out, retval] VARIANT_BOOL * pbForget)
This method is called once for
every record in the log. Note that the entries are in reverse order. The Compensator
can then undo changes made by the Worker, from the latest to the earliest, and can, of
course, remove the record from the log by returning TRUE.
Step 3: Finalizing Abortion
EndAbortVariants()
EndAbortVariants()
notifies the Compensator
that it
has received all log records and the abort is complete.
Handling Recovery
We have discussed that the successful completion of the Prepare
phase is a commitment, and the Compensator
is obligated to commit the
transaction. But what happens if a CRM never can commit its transaction?
Suppose some resource it depends on becomes forever unavailable moments after
the Prepare
phase, forever dooming its attempts to commit. COM+ runs your CRM,
asks it to commit every time a Worker
calls RegisterCompensator()
,
and identifies your particular class of Compensator
.
There is no simple answer. Ideally, you design your Compensator
such that after it has completed the Prepare
phase, it is certain to be able to
complete the commit. However, your Compensator
might rely on some technology
or OS service that proves flaky. To protect against the possibility of never
being able to commit, you can check the recovery argument of BeginCommitVariants()
.
If it is TRUE, you know that the CRM crashed during a previous
attempt to commit. You can keep
track of how many times the commit has failed by adding additional entries to
the log. Remember that the ICrmLogControl interface is given to the Compensator
after it is instantiated (COM+ calls its SetLogControlVariants
method), and so, like the Worker
, it has the capability of adding entries.
These entries can be interpreted by the Compensator on a
subsequent commit attempt; after a number of attempts, your CRM might throw up
its hands and report a completed commit to get COM+ off its back. It might then
send an email to the system administrator or write to some system event log, "Critical Failure: I tried and I tried, but was never able to commit. Data
modifications that certain objects thought happened never did. Human
intervention is required."
There is one final important
point I want to make about recovery�that of idempotence. You won�t find the term idempotence
in the dictionary, but you will hear it bantered about in mathematical and
development circles. An action is said to be idempotent ifit can occur any
number of times, but the frequency does not affect the result. For example, if
you hit the elevator Up button in the lobby, get impatient, and keep hitting
it, your action is idempotent�no matter how many times you hit the button, the
elevator
does not move any faster and the ultimate outcome is the same.
In the event of crash recovery, it is possible that your Compensator
is asked to commit changes more than once, so make sure that entries to the log
are written in such a way as to be idempotent. For example, if the log
indicates that an account should be debited once by $50, there is the
possibility that the Compensator
will end up debiting the account
repeatedly if asked to commit multiple times during the recovery process. It is
better if the log entries indicate the account�s last balance, as well as the
amount it should be debited by. In this way, the Compensator
can check
the account balance, and if it sees the balance is already reduced by $50,
choose not to debit the amount
again. Alternatively, it might be better design for the Worker
to
specify what the new balance of the account should be, including the $50 debit.
Setting the absolute balance is idempotent because no matter how many times you
do it, the effect is same.
When In Doubt
In the process of a two-phase commit, there is a period of time
between the Prepare
phase and the Commit
phase where an RM or CRM can be said
to be in doubt about the ultimate success of the distributed transaction. This
occurs when a transaction is spanning more than one RM on more than one
machine�thus, more than one DTC is involved. If you have a transaction spanning
two machines, A and B, each machine has its own DTC that controls the RM on
that machine. The primary or controlling DTC communicates with the DTCs on other machines,
propagates prepare and
commit notifications to the other DTCs, and coordinates the responses to these
commands. During a distributed transaction, RMs become interdependent as their
DTCs must gather consensus and coordinate with one another to make sure each RM
completes its Prepare
phase.
Thus, an RM might get a prepare
notification from its DTC, complete it, but wait for some time while other
participating RMs are completing their Prepare phases. The prepared RM has said
it is ready to commit, but the possibility still exists that another RM might
fail its Prepare
phase or crash. If this happens, the prepared RM does not get
a commit command from the DTC; rather, it is asked to roll back. On the other
hand, if all the other RMs do complete their Prepare
phase, the controlling DTC
issues a commit notification, and the prepared RM enters its Commit
phase. The
period of time for an RM, after the preparation is complete but before a commit
command is received from the DTC, is called the in-doubt state because the RM, though ready, is not sure (or is
in doubt) about whether the transaction will ultimately succeed.
Doubt should be fleeting and
probably last no more than a fraction of a second. However, if an RM crashes
during the in-doubt state, the DTC records that the whole distributed
transaction becomes in doubt. When the failed RM is next run, it contacts the
DTC, asks about the status of the transaction, and both parties try to
reconcile the transaction. If the other RMs complete their Prepare phase and
the controlling DTC issues a commit command, the recovering RM is on the hook
to commit its trans-action. If, after reawakening, the failed RM discovers that
another RM has not completed its Prepare
phase, it rolls back all changes made
during the Prepare
phase.
The Complete Compensator
Now that we�ve stepped through
the phases of Compensator
committal and abortion, the complete listing of
our XML Compensator
should make sense. Listing 9.4 details the
implementation of the XML sample Compensator
. The listing is somewhat long, but not
especially complex; the source file comments will provide a running commentary.
Listing 9.4 CRM
Compensator Component that
Commits or Aborts Changes to an XML File Made by the Worker
Option Explicit
Implements ICrmCompensatorVariants
Dim CrmLogControl As ICrmLogControl
Dim xmlDoc As New DOMDocument
Dim xmlNodes As IXMLDOMNodeList
Dim xmlNode As IXMLDOMNode
Dim xmlAccountNode As IXMLDOMNode
Dim xmlAccountBalanceNode As IXMLDOMNode
Dim xmlAccountNumberNode As IXMLDOMNode
Dim xmlRootNode As IXMLDOMNode
Dim vntCommand, vntAccountNumber, vntBalanceBefore, vntFilename
Private Function ICrmCompensatorVariants_AbortRecordVariants(pLogRecord As �Variant) As Boolean
Dim iNode As Integer
vntCommand = pLogRecord(0)
vntAccountNumber = pLogRecord(1)
vntBalanceBefore = pLogRecord(2)
vntFilename = pLogRecord(3)
On Error GoTo AbortRecordVariantProblem
If vntCommand = ADD_ACCOUNT_COMMAND Then
xmlDoc.Load vntFilename
Set xmlNodes = xmlDoc.childNodes.Item(0).childNodes
For iNode = 0 To xmlNodes.length - 1
Set xmlNode = xmlNodes.Item(iNode).childNodes.Item(0)
If xmlNode.Text = vntAccountNumber Then
Exit For
End If
Next
If xmlNode.Text <> vntAccountNumber Then
Exit Function
End If
xmlDoc.childNodes.Item(0).removeChild xmlNodes.Item(iNode)
xmlDoc.save vntFilename
ElseIf Command =
xmlDoc.Load vntFilename
Set xmlNodes = xmlDoc.childNodes.Item(0).childNodes
For iNode = 0 To xmlNodes.length - 1
Set xmlNode = xmlNodes.Item(iNode).childNodes.Item(0)
If xmlNode.Text = vntAccountNumber Then
ICrmCompensatorVariants_AbortRecordVariants = True
Exit Function
End If
Next
Set xmlRootNode = xmlDoc.documentElement
Set xmlAccountNode = xmlDoc.createElement("ACCOUNT")
Set xmlAccountNumberNode = xmlDoc.createElement("ACCOUNTNUMBER")
Set xmlAccountBalanceNode = xmlDoc.createElement("BALANCE")
xmlAccountBalanceNode.Text = vntBalanceBefore
xmlAccountNumberNode.Text = vntAccountNumber
xmlAccountNode.appendChild xmlAccountNumberNode
xmlAccountNode.appendChild xmlAccountBalanceNode
xmlRootNode.appendChild xmlAccountNode
xmlDoc.save vntFilename
ElseIf vntCommand = CHANGE_ACCOUNT_COMMAND Then
xmlDoc.Load vntFilename
Set xmlNodes = xmlDoc.childNodes.Item(0).childNodes
For iNode = 0 To xmlNodes.length - 1
Set xmlNode = xmlNodes.Item(iNode).childNodes.Item(0)
If xmlNode.Text = vntAccountNumber Then
Exit For
End If
Next
If xmlNode.Text <> vntAccountNumber Then
ICrmCompensatorVariants_AbortRecordVariants = True
End If
Set xmlNode = xmlNodes.Item(iNode).childNodes.Item(1)
xmlNode.Text = vntBalanceBefore
xmlDoc.save vntFilename
End If
ICrmCompensatorVariants_AbortRecordVariants = True
Exit Function
AbortRecordVariantProblem:
MsgBox "XMLWorker, AbortRecordVariant error: " & Err.Description
End Function
Private Sub ICrmCompensatorVariants_BeginAbortVariants(ByVal bRecovery As Boolean)
End Sub
Private Sub ICrmCompensatorVariants_BeginCommitVariants(ByVal bRecovery As �Boolean)
End Sub
Private Sub ICrmCompensatorVariants_BeginPrepareVariants()
End Sub
Private Function ICrmCompensatorVariants_CommitRecordVariants(pLogRecord As �Variant) As Boolean
ICrmCompensatorVariants_CommitRecordVariants = False
End Function
Private Sub ICrmCompensatorVariants_EndAbortVariants()
End Sub
Private Sub ICrmCompensatorVariants_EndCommitVariants()
End Sub
Private Function ICrmCompensatorVariants_EndPrepareVariants() As Boolean
ICrmCompensatorVariants_EndPrepareVariants = True
End Function
Private Function ICrmCompensatorVariants_PrepareRecordVariants(pLogRecord As �Variant) As Boolean
End Function
Private Sub ICrmCompensatorVariants_SetLogControlVariants(ByVal pLogControl As COMSVCSLib.ICrmLogControl)
Set CrmLogControl = pLogControl
End Sub
Listing 9.5: XML
Wrapper Component (This Allows Clients to Explicitly Abort or Commit the
Transactions of The Worker Component):
Option Explicit
Const CONTEXT_E_ABORTING = &H8004E003
Dim pObjectContext As ObjectContext
Dim pObjectState As IContextState
Dim XMLWorker As XMLWorkerVB
Private Sub Class_Initialize()
Set pObjectContext = GetObjectContext()
Set pObjectState = pObjectContext
Set XMLWorker = New XMLWorkerVB
End Sub
Public Sub AddAccount(AccountNumber As Long, Balance As Double, filename As �String)
On Error GoTo addproblem
XMLWorker.AddAccount AccountNumber, Balance, filename
Exit Sub
addproblem:
Call ErrorHandler
End Sub
Public Sub RemoveAccount(AccountNumber As Long, filename As String)
On Error GoTo removeproblem
XMLWorker.RemoveAccount AccountNumber, filename
Exit Sub
removeproblem:
Call ErrorHandler
End Sub
Public Sub CreditAccount(AccountNumber As Long, Amount As Double, filename As �String)
On Error GoTo creditproblem
XMLWorker.CreditAccount AccountNumber, Amount, filename
Exit Sub
creditproblem:
Call ErrorHandler
End Sub
Public Sub DebitAccount(AccountNumber As Long, Amount As Double, filename As �String)
On Error GoTo debitproblem
XMLWorker.DebitAccount AccountNumber, Amount, filename
Exit Sub
debitproblem:
Call ErrorHandler
End Sub
Public Sub Abort()
pObjectContext.SetAbort
End Sub
Public Sub Commit()
pObjectContext.SetComplete
End Sub
Private Sub ErrorHandler()
If Err.Number = CONTEXT_E_ABORTING Then
MsgBox "Could not perform the operation. The transaction has been �aborted."
pObjectContext.SetAbort
Else
MsgBox Err.Number & " : " & Err.Description
End If
End Sub
CRMs and Isolation
One critical property of a
transaction, isolation, is not automatically
provided by the CRM. Isolation implies that what happens inside a transaction
is hidden and protected. Normally, an RM places some form of lock on the
resources involved. Most relational databases, for example, provide row level locking so that rows whose data is involved in a
transaction are protected from modification by clients outside the transaction.
The CRM architecture, however, does not provide any facility to aid the
developer in terms of enforcing isolation. This is not to say that
synchronization support is not provided�it is. All COM+ transactions are
synchronous and have only one logical thread (transactions always run a COM+ Activity, discussed in Appendix B,
"COM+ SynchronizationThrough
Activities"). But don�t confuse synchronization and concurrency with isolation;
they are altogether different.
Although you can be certain that only one method call will execute
on the Worker
of your CRM at any one time, you cannot be certain where that method will come
from. For example, it is possible that two transactions executing concurrently
involve the same CRM. Unlike a true RM whose interfaces have method arguments
to keep track of transaction IDs, the CRM architecture does not include any
direct facility to keep track of what objects are calling into it or what
transactions they are involved in. Thus, Object A from Transaction T1 might ask
an instance of your CRM to make a modification to some region of data in one
method call, and Object B from Transaction T2 might ask for a change to the
same region in another. Although different instances of your CRM might be used,
the same underlying data source is being modified in both cases. The CRM,
therefore, needs to employ some method of protecting specific regions of the
data source involved in a transaction and blocking would-be datamodification
requests from clients outside of that transaction.
You can choose whatever method you want to enforce isolation. File
locking, or some other form of OS lock, often works well. Just keep in mind
that a CRM is nota singleton, and you cannot count on the same instance of a
CRM servicing different clients in different transactions. So, if you are a C++
developer and want to employ mutexes, semaphores, critical sections, and so on,
make sure they are named so that they are at system-wide scope and can
synchronize access across multiple processes.
The CRMREGFLAG_FAILIFINDOUBTSREMAIN Flag
It is possible that some form of crash, network outage, and so on
can result in a CRM that, after recovering, is unsure about the success or
failure of a transaction it participated in. If you want to prevent your CRM
from being involved in any new transactions while a pre-existing transaction is
still in doubt, you need only code the following:
Clerk.RegisterCompensator "MyCompensator.Comp", "[put your description here]",
�CRMREGFLAG_ALLPHASES OR CRMREGFLAG_FAILIFINDOUBTS
Summary
If a relational database system is capable of handling
transactions, and it supports the OLE Transaction two-phase commit protocol, it
is considered by COM+ to be an RM. If an RDBMS only supports the XA protocol,
but its ODBC driver (or some other form of Resource Dispenser) can convert an
XA to an OLE Transaction, this database also qualifies as an RM.
An RM does not necessarily need to be a relational database system,
however. The CRM is an example of an RM that can make changes to any structure
of data within the context of a COM+ transaction. The CRM is a template and
protocol that a developer can use to simplify the complex task of writing an
RM.
The author of the CRM must
implement two coclasses: the Worker
and the Compensator
. The Worker
is instantiated and used by any client executable
or object to make changes to some resource kept by a given CRM. When COM+
attempts to commit a transaction involving a CRM, it instantiates an instance
of the Compensator
and interacts with it during the Prepare
and
Commit
phases of a transaction. Typically, the Worker
is responsible for making initial changes of
state to underlying data, and the Compensator
is responsible for bringing this change to its
final state and making such changes permanent. Both the Worker
and Compensator
can
write to a log maintained on their behalf by COM+, but only the Compensator
is
presented with the actual entries of this log. The log, kept by the CRMClerk
object, is the only form of communication between the Worker
and Compensator
,
and COM+ gives an interface (ICrmLogControl) to both objects upon their instantiation.
A Worker
can
explicitly abort the transaction by calling the ForceTransactionToAbort()
method of the CRMClerk
, but the Compensator
may not
call this method. The Compensator
might indicate failure by returning FALSE
during the final steps of the Prepare
phase, or it can simply crash. If,
however, the Compensator
successfully completes its Prepare
phase, it is
obligated to commit its changes if asked.
COM+ does not provide isolation for CRMs. There can be any number
of CRM instances running at a given time, so CRM authors should take care to
protect data relevant in a particular transaction from corruption by another
CRM instance in a different transaction. Additionally, the CRM author must make
sure that actions made by the Compensator
are idempotent�that is, they
can safely be implemented more than once without negative consequence.
Enabling
A CRM
For components of a COM+
application to use a CRM, the "Enable Compensating Resource Managers" check box needs to be clicked on the Advanced tab of
the application�s (that is, the one hosting the CRM)� properties.
For additional configuration settings, including transactional and
JIT settings for the worker and compensator, the MSDN offers a concise list so
there is no need to duplicate it here.�
Simply query MSDN for the string, "Installing CRM Components."
Copyright � 2000 New Riders Publishing