Overview: How COM+ manages
transactions & state
I will attempt to give a quick
overview of how COM+ goes about its business. Quite a few articles and books are
available on the topic by acclaimed authors. I can recommend the book Programming Distributed
Applications with COM+ and Microsoft Visual Basic 6 by Ted Pattison, or an
article he wrote for Microsoft Systems Journal in October 1999, titled
Writing COM+-style Transactions with Visual Basic and SQL Server. The following overview provides a very brief summary of the
key functionality of COM+. Understanding
the impact of each of these functions plays a crucial role in understanding how
to use them in your reusable objects.
COM+ provides a programming
model based on declarative transactions.
the MTSTransactionMode property of the class sets an object’s
transaction mode at design time. When
the COM+ runtime creates a new object, it examines the component’s
setting to determine whether the new object should be created inside a
transaction. Once an object has
been created, you cannot add it to a transaction, neither can you disassociate
it from the transaction in which it has been created.
Once an object is created inside a transaction, it spends its entire
lifetime inside this transaction. When
the transaction is committed or aborted, the COM+ runtime destroys all the
objects inside it.
When the COM+ runtime receives
a request to create a new object, it inspects the transaction context of the
creator to determine whether the creator is running inside a transaction.
It also inspects the transaction support attribute of the new object.
Using these two pieces of information it decides whether to create the
object 1) inside a new transaction, 2) inside the same transaction as its
creator or 3) without a transaction.
Figure 4 shows a diagram of a client application connecting to an object (the
root object) through a COM+ context.
The root object in turn instantiated a secondary object in the same COM+
transaction. The root object
connects to the secondary object through another COM+ context.
4 also shows three important Boolean values that COM+ uses to manage the
scope of the transaction and the object lifetime.
4 these flags are set to their default values.
For a detailed discussion on how COM+ uses this three flags, refer to
chapter 10 of the book by Ted Pattison mentioned above.
Figure 4: Root
object and Secondary object in COM+ transaction
The root COM+ object plays a
very important role in a COM+ transaction. Every transaction as a whole
maintains a Boolean value (MustAbort) that indicates whether the
transaction should be aborted. This
value is initially set to False when the transaction is created.
COM+ inspects the value of MustAbort when:
- The root object returns control to its caller.
If MustAbort is False nothing happens.
If it is True, COM+ aborts the transaction and deactivates all the
secondary objects inside the transaction.
- The root object is deactivated.
Depending on the value of MustAbort,
COM+ will then either commit or abort the transaction.
Whatever the outcome of the transaction, when the transaction completes, COM+
will recycle the object(s) that took part in the transaction.
This means that the lifetime of the transaction plays an enormous part in
designing the interaction between objects partaking in either the same or
There are two method pairs
available to the developer with which to control the transaction scope and
outcome. These are
The DisableCommit and
methods allows you to set the value of the IsHappy
flag. When an object running inside
a transaction is deactivated, and IsHappy is False, the transaction’s
flag is set to true, which will cause the transaction to be aborted.
Note that this is method only sets the value of IsHappy, it does
not perform any other logic. This
means that the value of this flag can be changed multiple times, but the value
is only taken into account when the object is deactivated.
The SetComplete and SetAbort
methods sets the value of IsHappy to either True or
False, plus it sets the value of IsDone to True.
The value of the IsDone flag is examined every time an object
returns control to its caller. If
the IsDone value is True, COM+ deactivates this object, and
the normal process to determine whether to commit or abort the transaction is
From the above it is clear
that, if you are developing an object that may be reused by other objects on the
BO level, care has to be taken when calling SetComplete and SetAbort.
If your object participates in a transaction as a secondary object, and
or SetAbort is called, this object will be recycled (and the instance
state will be lost) when it returns control to the calling object.
This may not be what the calling object was expecting.
An example of this is: In Object B, the Save method does a
at successful completion. Object
A uses an instance of Object B it loads an instance of Object
B, does some work and saves, it does some more work and want to save it
again. Problem is that after the
first time Save is executed, Object B is recycled when it returns
control to Object A, and the instance state is lost.
Object A is not aware of this, as it actually connects to the
context and not directly to Object B.
The next time Object A tries to access any method or property of Object
B, a new instance of Object B is created within the existing object
context. Object A is totally
unaware that it is now working with another instance of Object B.
If Object A now performs logic that is dependant on the state of Object
B, it will produce erroneous results.
Issues such as the above means
that you cannot call SetComplete unless you are absolutely sure that no
other object expects you to hold your state.
As your component will be reused on a binary level, it has no guarantee
who uses it or exactly how it is being used.
This means that great care has to be taken when calling SetComplete
in your component, and that you have to inform any developer of another BO-level
calling component of this fact, at design time.
You can alert the user of your component to the fact that you are using
in a method by documentation or a naming convention.
Any BO level component should
always only be accessed by its CO counterpart component from the CO level.
Because a component and its CO level partner will always be designed and
implemented together, the CO object can take the usage of SetComplete
into account in the design stage.
When designing interaction
between COM+ objects, I make use the following rules (which I like to refer to
as the COM+ State Rules) :
- Non Transactional COM+ objects can maintain instance state
- Transactional COM+ objects maintains instance state for the
duration of the transaction
- Transactional COM+ objects cannot maintain instance state across
- Transaction scope can be managed by object scope
- Transaction scope can be managed by calling SetComplete/SetAbort
- Acquire resources late, release them early
The core concept behind the
method I follow is to realize other BO objects as the primary users of your
reusable BO object, with the BO object’s own CO counterpart seen as a
secondary user of the object. This
is quite a significant paradigm shift from seeing the CO object as the primary
user of its BO counterpart. When
the CO object is seen as the primary user, all methods on the BO object is
normally geared towards a) getting state information from the CO, b) performing
an action on this state and c) returning the changed state to the CO.
When you view other BO objects as the primary users of your BO level
object, the interfaces to the functionality provided by the object changes from
simple Get-In, Do-your-stuff and Return-Results type of approach, to a
full OOP interface. The BO object needs to expose its methods and properties via
an OOP interface to other BO objects. All
the functionality available to other BO level objects must however also be
available to the object’s own CO counterpart. There must exist only one set of business logic on the BO
layer, accessible to the BO and the CO layer in the following ways:
- A BO object (Object A) can instantiate an instance of another BO
object (Object B). Object B
exposes an OOP interface to Object A through which Object A is able to call
methods and set and retrieve properties of Object B.
Object A can control the scope of the instance of Object B and by
doing so, the scope of the transaction
- A CO object is able to call the business logic in its
corresponding BO object via a wrapper function. It calls a function on the BO object and passes its
instance state into this function in one or more state variables.
The function executes the same method exposed to other BO level
objects, and when it is done, the CO can retrieve the changed instance state
from the state variables. The
CO object does not expect the Bo object to maintain state between method
calls, and sends and retrieves the instance state to and from the BO object
with every function call.
The above requirements are met
by following a very simple recipe on the BO object. The functionality on BO level is provided in plain object
oriented fashion. The BO object has
property procedures with private variables to maintain the instance state, and
functionality is provided as methods that act on the properties of the object
(and any secondary objects). Any BO object
is trusted to never call SetComplete when accessed by other BO objects
will examine the use of SetAbort in more detail when we look at error
If you have a look at the COM+ State Rules, you will see that a
root object then only has to worry about the scope of its secondary objects, as
object instance state and transaction management is done using the functionality
provided by COM+.
CO access to the methods on the
BO is provided by a wrapper function on the BO object.
Note that I refer to it as a wrapper function and not a
method. This is purely a
naming convention I use to distinguish between the methods exposed for use by
other BO objects (called methods), and the methods exposed for use by CO objects
(I call these functions). The
reason I refer to this specific type of method as a function is because it
receives all the information it needs via the parameter list, as opposed to a
normal method that acts on the existing state of an object.
So, this wrapper function is implemented as a normal method of the
object, but it takes as input a variable or variables carrying the total
instance state of the object. This
wrapper function is very simple, as it performs a maximum of three tasks:
The instance state as sent
from the CO object is copied into the local state variables of the BO object
The corresponding method is
The local instance state is
copied from the local variables into the By Reference variables from
where the CO object will have access to them
on the logic in the method, step 1 or 3 may be left out.
If the method is used to retrieve instance state from the database, step
1 may be left out, while step 3 may be left out if the method does not make any
changes to the instance state of the object.
CO object, the methods that call the functions on the BO object follow a similar
pattern. The methods generally consist of the following steps
- The local instance state is packed into a variable
- An instance of the BO object is created
- The BO function is called and the local instance state is passed
to this function
- The BO object is destroyed
- The instance state is unpacked into the local variables
Depending on the logic of the
method executed, it may not be necessary to send state to the BO method (when
loading the object from the database for example), or to retrieve state back
from the method (when deleting the object from the database)
So, for every method on the BO
object, there is a function call to expose this method to its CO object.
In order to distinguish between the function and the method, I use the
following naming convention: The method and the function gets the same name,
with the prefix f_ added to the start of the name of the function,
indicating that this is the wrapper function and only to be called from the CO