COM+ is an advanced TPM that provides your components with easy-to-use administrative configuration for your transactional needs. COM+ encapsulates the underlying transaction monitoring and coordination required to manage a transaction. The COM+ transactions architecture defines a few basic concepts you need to understand to take advantage of COM+ transactions support: resource managers, the transaction root, the two-phase commit protocol, and the Distributed Transaction Coordinator (DTC).
A resource (such as a database management system) that can participate in a COM+ transaction is called a resource manager. A resource manager knows how to conduct itself properly in the scope of a COM+ transaction—it records the changes done by your application’s objects and will only commit the changes when told to do so. A resource manager knows how to discard the changes and revert to its previous state if it is told to roll back. A resource manager can auto-enlist in a transaction—the resource manager can detect it is being accessed by a transaction and enlist itself in it. Every COM+ transaction has a unique transaction ID (a GUID), created by COM+ at the beginning of the transaction. The resource manager keeps track of the transaction ID and will not enlist twice. Auto-enlisting means that your components are not required to explicitly enlist the resources needed for a transaction; therefore, they do not have to deal with the problem of multiple objects accessing the same resource, not knowing whether or not it is already enlisted in the transaction.
A resource manager must store its data in a durable storage to maintain the transaction durability. To maintain the transaction’s isolation, a resource manager must lock all data (such as rows, tables, and queues) touched by the transaction, and allow only objects that take part in that transaction to access that data. Note that all the hard work required to manage a resource manager is hidden from your components. The burden is on the resource manager’s shoulders, not yours.
A resource manager must vote on the transaction’s result. Once the transaction is over, COM+ asks each participating resource manager, “If you were asked to commit the changes, could you?”. A resource manager is represented by a system service that manages the resource, and your objects access the resource manager via a proxy.
Quite a few resources today comply with these requirements: first and foremost is Microsoft SQL Server (Versions 6.5 and above), but other non-Microsoft databases, such as Oracle 8i and IBM DB2, are COM+ resource managers as well. A resource manager does not have to be a database; for example, Microsoft Message Queue (MSMQ) is a resource manager.
When multiple objects take part in a transaction, one of them has to be the first to ask that a transaction be created to contain the operation (usually a client’s request). That first object is called the transaction root. A given transaction has exactly one root (see Figure 4-4).
Designating an object as a transaction’s root, or as an internal object, is done administratively. The component’s developer configures it to either not take part in transactions; to require a transaction, (to join an existing transaction if one exists); or to start a new transaction if none exists. If the component starts a new transaction, then it becomes the root of that transaction. The developer can also configure the component to always start a new transaction—to always be the root of a new transaction.
Once a transaction is created, when Object A in Transaction T1 creates another object, Object B, according to B’s configuration, it will:
Be part of Transaction T1.
Not be part of T1 or any other transaction. This may compromise isolation and consistency because B can perform operations that will persist even if T1 aborts. Also, B has no way of deciding whether T1 should abort in case B has an error.
Start a new Transaction T2. In that case, Object B becomes the root of the new transaction. This option may also compromise isolation and consistency, as one transaction could commit and the other one could abort independently of the other.
Neither A nor B needs to actively do anything to decide on the transaction. COM+ checks the object’s configuration and places it in the correct transaction automatically.
COM+
uses a transaction management protocol called
the two-phase commit
to decide on a transaction
result, commit changes to the system state, and enforce
atomicity and consistency. The
two-phase commit protocol enables COM+ to support transactions that
involve multiple resources.
After the transaction’s root starts a new transaction, COM+ stays out of the way. New objects may join the transaction, and every resource manager accessed automatically enlists itself with that transaction. The objects execute business logic and the resource managers record the changes made under the scope of the transaction. You already saw that all the application’s objects in a transaction must vote during the transaction for whether the transaction should abort (if the objects had an error) or be allowed to commit (if the objects have done their work successfully). Again, abstaining from voting on the transaction’s outcome is not an option for any object in the transaction. A transaction ends when the root object is released (or deactivated, when you’re using JITA). At that point, COM+ steps back into the picture and checks the combined vote of the participating objects. If any object voted to abort, the transaction is terminated. All participating resource managers are instructed to roll back the changes made during the transaction.
If all the objects in the transaction vote to commit, the two-phase commit protocol starts. In the first phase, COM+ asks all the resource managers that took part in the transaction if they have any reservations in committing the changes recorded during the transaction. Note that COM+ is not instructing the resource managers to commit the changes. COM+ merely asks for their vote on the matter. At the end of the first phase, COM+ has the combined vote of the resource managers. The second phase of the protocol acts upon that combined vote. If all resource managers voted to commit the transaction in the first phase, then COM+ would instruct all of them to commit the changes. If even one of the resource managers said in phase one that it could not commit the changes, then in phase two, COM+ would instruct all the resource managers to roll back the changes made, thus aborting the transaction.
It is important to emphasize that a resource manager’s vote that has no reservations about committing is special: it is an unbreakable promise. If a resource manager votes to commit a transaction, it means that it cannot fail if, in the second phase, COM+ instructs it to commit. The resource manager should verify before voting to commit that all the changes are consistent and legitimate. A resource manager never goes back on its vote. This is the basis for enabling transactions. The various resource manager vendors have gone to great lengths to implement this behavior exactly.
As demonstrated in the transaction scenarios described previously, there is a clear need to coordinate a transaction in a distributed environment, to monitor the objects and resources in the transaction, and to manage the two-phase commit. Managing the interaction between the components (by collecting their votes) is done by COM+; managing the two-phase commit protocol is done by the Distributed Transaction Coordinator (DTC). The DTC is a system service tightly integrated with COM+. The DTC creates new transactions, propagates transactions across machines, collects resource managers’ votes, and instructs resource managers to roll back or commit.
Every machine running COM+ has a DTC system service. When an object that is part of a transaction on Machine A tries to access another object or a resource on Machine B, it actually has a proxy to the remote object or resource. That proxy propagates the transaction ID to the object/resource stub on Machine B. The stub contacts the local DTC on Machine B, passing it the transaction ID and informing it to start managing that transaction on Machine B. Because the transaction ID gets propagated to Machine B, resource managers on Machine B can now auto-enlist with it.
When the transaction is done, COM+ examines the combined transaction vote of all participating objects. If the combined vote decides to abort the transaction, COM+ instructs all the participating resource managers on all participating machines to roll back their changes. If the combined objects’ vote was to try to commit the transaction, then it is time to start the two-phase commit protocol. The DTC on the root machine collects the resource managers’ votes on the root machine and contacts the DTC on every machine that took part in the transaction, instructing them to conduct the first phase on their machines (see Figure 4-5). The DTCs on the remote machines collect the resource managers’ votes on their machines and forward the results back to the DTC on the root machine.
After the DTC on the root machine receives the results from all the remote DTCs, it has the combined resource managers’ vote. If all of them voted to commit, then the DTC on the root machine again contacts all the DTCs on the remote machines, instructing them to conduct phase two on their respective machines and to commit the transaction. If, however, even one resource manager voted to abort the transaction, then the DTC on the root machine informs all the DTCs on the remote machines to conduct phase two on their respective machines and abort the transaction. Note that only the DTC on the root machine has the combined vote of phase one, and only it can instruct the final abort or commit.
A given transaction can contain objects from multiple contexts, apartments, processes, and machines (see Figure 4-6).
Figure 4-6. A transaction (whose scope is indicated by the dashed line) is unrelated to machine, process, apartment, and context
Each COM+ context belongs to no more than one transaction, and maybe none at all. COM+ dedicates a single bit in the context object (discussed in Chapter 2) for transaction voting. An object votes on a transaction’s outcome (whether to proceed to phase one of the two-phase commit protocol or to abort) by setting the value of that bit. As a result, a transactional object must have its own private context. Two transactional objects cannot share a context because they only have one bit to vote with. If two objects share a context and one of them wants to abort and the other wants to commit, then you would have a problem. Therefore, each COM+ object belongs to at most one transaction (because it belongs to exactly one context) and an object can only vote on the outcome of its own transaction. Collecting the object’s vote is done by the context’s interceptor when the object is released or deactivated.
The context object has more to do with the transaction than just
holding the object’s vote bit. Internally, each context object
stores references to the transaction it belongs to, if any exist. The
context object stores the transaction’s ID and a pointer to the
transaction object itself. Every transaction is represented by an
interface called
ITransaction
, and the context object stores an
ITransaction*
pointer to the current transaction
it belongs to. You can gain access to that information by accessing
the context object and obtaining the
IObjectContextInfo
(first presented in Chapter 2), defined as:
interface IObjectContextInfo : IUnknown { BOOL IsInTransaction( ); HRESULT GetTransaction(IUnknown** ppTransaction); HRESULT GetTransactionId([out] GUID* pTransactionID); HRESULT GetActivityId([out] GUID* pActivityID); HRESULT GetContextId([out] GUID* pContextId); };
The GetTransactionId( )
method returns the transaction ID (a GUID). The
IsInTransaction( )
method returns TRUE
if the context is included in
a transaction. The GetTransaction( )
method returns a pointer to the current transaction this context is
part of, in the form of a ITransaction*
interface
pointer.
A full discussion of the
ITransaction
interface is beyond the scope of this
chapter. It is used by resource managers to auto-enlist in a
transaction and to vote during the two-phase commit protocol.
Briefly, when the object accesses a resource manager, it does so via
a proxy. The resource manager’s proxy retrieves the transaction
ID and the ITransaction*
pointer from the context
object and forwards them to the resource manager for auto-enlistment.
The resource manger looks at the transaction ID. If it is already
enlisted in that transaction, then it does nothing. However, if this
is the first time the resource manager is accessed by that
transaction, it uses the ITransaction*
pointer to
enlist.
The benefits of COM+ transactions architecture were implied in the previous discussion of the architecture’s elements. Now that you have the comprehensive picture, you can see that the main benefits are as follows:
Auto-enlistment of resource managers saves you the trouble of making sure that resources are enlisted exactly once. Otherwise, components would be coupled to one another by having to coordinate who enlists what resource and when.
An object and its client do not ever need to know what the other objects are doing, whether they require transactions, or what another object’s vote is. COM+ places objects in transactions automatically, according to their configuration. COM+ collects the objects’ votes and rollback changes. All an object has to do is vote.
The programming model is simplified, robust, easier, and faster to implement.
The COM+ transactions architecture decouples the components from specific TPM calls. There is nothing in the components’ code that relates to the DTC or to transaction management.
Get COM & .NET Component Services now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.