Applications differ greatly in their complexity and need for COM+ transactions support. To understand the COM+ transactions architecture and the needs it addresses, you should first examine a few generic transaction cases.
Consider an application that comprises just one component instance, an object that processes a client’s request and accesses a single resource (such as a database) that takes part in a transaction. This situation is depicted in Figure 4-1. The application (in this case, the object) has to inform the resource when a transaction is started. This act is called enlisting the resource in the transaction. The object starts making calls on the resource interfaces, making changes to its state. However, at this point the resource should only record (log) the changes and not actually perform them.
If the object encounters no errors when executing a client’s request, then on completion it informs the resource that it should try to commit the changes. If the object encounters errors, it should instruct the resource to abort and roll back the changes. Even if the object wants to commit the transaction, any existing errors on the resource side might cause the transaction to abort.
Note that only the application can request to commit the transaction, but either the application or the resource can abort it.
You can easily deal with a single object/single resource scenario on
your own without relying on COM+ transactions by making explicit
programmatic calls to enlist a resource in a transaction and
instructing it to commit or roll back at the end of the transaction.
Most resources support this sort of interaction out-of-the-box and
expose simple functions, such as
Suppose you have multiple objects in your application, each of which requires access to the same resource to service a particular client request. Suppose your design calls for containing all the changes the objects make to the resource in the same transaction, to ensure consistency of these multiple changes (see Figure 4-2).
Unfortunately, things get much more complicated than in the previous scenario. The main problem is coordination. Since the resource should be enlisted in the transaction just once, who should be responsible for enlisting it? Should it be the first object that accesses it? Or maybe it should be the first object that is created? How would the objects know and coordinate this information? In addition, since the objects can all be on different machines, how would you propagate the transaction from one machine to the next? How would the objects know what transaction they are in? What should you do if one machine crashes while the other machines continue to execute the client request?
Each of the objects can encounter errors and abort the transaction, and they ask the resource to commit the changes only if they all succeed. The problem here is deciding which object is responsible for collecting the votes. How would an object know that a transaction is over? Who is responsible for notifying the resource of the voting result—that is, instructing the resource to try to commit or roll back the changes? What should the objects do with their own state (their data members)? If the resource is unable to commit the changes, the transaction must abort; in that case, the objects’ state reflects inconsistent system state. Who will inform the objects to purge their inconsistent state? How would the objects know what part of their state constitutes system inconsistency?
Fortunately, COM+ transactions support makes this scenario as easy to deal with as the previous one. COM+ takes care of enlisting the resource, propagating the transaction across machine boundaries, collecting the components’ votes, and maintaining overall resource and object state consistency.
An enterprise application often consists of multiple objects accessing multiple resources within the same transaction (see Figure 4-3).
In addition to all the coordination challenges posed by the previous scenario, you now have to enlist all the resources just once in the transaction. Who keeps track of what resources are used? You definitely don’t want to have that knowledge in your code because it could change. Who is responsible for informing the resources about the transaction outcome (the components’ votes) and asking them to try to commit or abort? Since any one of the resources can refuse to commit the changes, how do you know about it and how would you instruct the other resources to roll back their changes? Your components and resources may all be on different machines, resulting in multiple points of failure. Transaction processing monitors (TPMs) have evolved to answer these challenges, but they require explicit calls from the application, which results in a cumbersome programming model.
Yet again, COM+ transactions support makes this situation as easy as the first one. Even in a distributed environment with multiple resources, your programming model is elegant and simple. It allows you to focus on your business logic while relying on COM+ to manage the transaction for you.