I’ll end this chapter by pointing out a few more pitfalls you should be aware of when designing and developing transactional components in COM+. Some of these pitfalls have already been implied elsewhere in this chapter, but elaborating on a pitfall is always a good idea.
A transactional component should avoid accessing resources that are not resource managers. Typical examples are the filesystem, the Registry, network calls, and user interaction such as printouts or message boxes. The reason is obvious—if the transaction aborts, changes made to those transaction-ignorant resources will persist and jeopardize system consistency.
You should avoid passing
subroot objects to any client
outside your transaction, be it the client that created the root or
any other client. You have to avoid this by design because COM+
allows you to stumble into the pitfall. The problem with sharing
subroot objects with clients outside of your transaction is that at
any moment the client that created the root object can release the
root object. A COM+ transaction requires a root to function, and the
root designation does not change, no matter how the transaction is
started. With the root gone, the transaction layout is defective. In
Figure 4-17, any call from Client B to Object 2 will
fail with the error code
CONTEXT_E_OLDREF
. The only thing Client B can do is
release its reference to Object 2.
You should avoid accessing COM+ objects outside your transaction, whether those objects are part of another transaction or not. Look at the objects layout in Figure 4-18.
In this figure, Object 1 has access to Object 2 and Object 3, both outside its transaction. The problem is that Transaction A could abort and Transaction B could commit. Object 3 acts based on its interaction with an object from an aborted transaction, and therefore Object 3 jeopardizes system consistency when its transaction commits. Similarly, when Object 1 accesses Object 2 (which does not have a transaction at all), Object 2 may operate based on inconsistent state if Transaction A aborts. In addition, the interaction between Object 1 and Object 2 is not well defined. For example, should Object 1 abort its transaction if Object 2 returns an error? For these reasons, objects should only access other objects within the same transaction.