How to Build Microsoft Transaction Server Components

When using Microsoft Transaction Server, think of an application in terms of components or objects. Component is the code that implements a COM object. This section describes classes, methods, and other object-oriented terms.

Overview of Object-Oriented Programming

Traditional languages, such as COBOL and C, represent a procedural approach to application development. These programs consist of two components, data and code. To improve maintenance, code is compartmentalized into procedures. As applications become more complex over time, each procedure can manipulate any part of the data. The interrelationships become hard to maintain and enhance. In the C language, for example, units of code are called functions, while data are referred to as structures. Because functions and structures are not formally connected in C, a C function can operate on more than one type of structure, and more than one function can operate on the same structure.

Object-oriented programming takes the procedural approach and refines it so that data and code form a single, indivisible object. This is called data encapsulation; the data is compartmentalized with the procedures for manipulating that data. Languages, such as Visual Basic 5, C++, Smalltalk, and Java, are examples of object-oriented languages.

By providing this modularity, programs are divided into distinct objects for specific tasks and data. Also, programmers can simultaneously work on the same project, assembling the components at milestones for testing. This is possible because all communication processing between objects is done using messages, which define the interface to the object. The interface is a group of logically related operations that provide access to a component object. For example, the spellchecker bundled with Microsoft Word can be accessed by other software, such as Microsoft Excel, the Microsoft PowerPoint®presentation graphics program, and Microsoft Exchange, by using its interface. Everything an object can do is represented by its message interface.

After being defined and tested, objects are not modified. Doing so would alter how the object works. For example, changing one object could cause a program to fail because other objects expect it to have certain characteristics. Providing access to an object only through its messages reduces effort, improves maintenance, and speeds debugging.

An object is specified using its class. A class defines object properties and methods used to control the object's behavior. Objects are individual instances of a class. For example, an object called Car is an individual instance of the class of Transportation. The Transportation class defines what it is to be a Car object, and all the car-related messages a car object can act upon.

You can make more than one object of this class, and call them Car, Truck, and so on. The Transportation class defines messages that the Transportation objects understand, such as "start," "stop," and "turn left." When a message is a set to execute an action, the executed code is called a method.

Object-oriented programming also allows you to define new classes of objects that build on the behavior of existing classes. This is called inheritance or subclassing. The new class inherits all the existing messages, and therefore, all the behavior of the original class. This promotes reuse, not only reducing the amount of code to write but the time to debug as well. For example, after creating the class Car, you might make a subclass called Sportscar that defines some sportscar-specific messages.

There are also times when you will need different classes to respond to the same message, but not in the same way. This is called polymorphism, and increases the flexibility of an application without increasing its complexity. Objects can also receive messages from a program as it executes. For example, every Windows application can copy and paste within and across applications. Any number of software programs can support these functions by calling the appropriate class.

Beyond COM

Microsoft Transaction Server components are distinguished from other COM components in that they execute in the Transaction Server run-time environment. Transaction Server also imposes specific requirements on components beyond those required by COM. For example, Visual C++ components are implemented as classes. Likewise, Visual Basic components are implemented by class modules. To begin with, the component must be a dynamic-link library (DLL). Components that are implemented as executable files (.exe files) cannot execute in the Transaction Server run-time environment.

This means that if you have a Visual Basic Remote Automation server executable file, it must be rebuilt as a DLL for use by Microsoft Transaction Server.

For Visual C++ components, there are additional requirements:

Unlike other TP monitors, Microsoft Transaction Server leverages existing Microsoft ActiveX APIs, familiar to desktop developers, to lower training costs and shorten ramp-up time. Simple APIs make it quick to learn and implement:

Transaction Server has only two new APIs, GetObjectContext and SafeRef. Most Transaction Server applications can be built using just one new API and one new interface.

Component Packaging

After you have written the software components, you must assemble them into a single solution. Microsoft Transaction Server introduces the concept of component packages to enable the easy integration of different software components into a single application. These packages are created and deployed by using Microsoft Transaction Server Explorer, a graphical administration tool for components.

Packages enable components to be grouped and distributed as a solution, which provides a high degree of performance, load balancing, and fault isolation. Components in a package:

Transaction Server also makes it easy to build distributed applications by providing location transparency. Transaction Server assumes responsibility for loading the component into a process environment. A Transaction Server component can be loaded into a client application process (in-process component or DLL), or into a separate surrogate server process environment, either on the client's computer (local component) or on another computer (remote component).

Every Transaction Server component has a transaction attribute recorded in the Transaction Server catalog. The catalog maintains configuration information for components, packages, and roles. To administer the catalog, use Microsoft Transaction Server Explorer.

Every Microsoft Transaction Server component has a transaction attribute, which is set in Explorer. Transaction Server uses this attribute during object creation to determine whether the object should be created to execute within a transaction, and whether a transaction is required or optional.

You set the Transaction Attribute as part of a component definition in Explorer. You can also set a transaction attribute at development time using values defined in Mtxattr.h. You can specify these values in an ODL file to encode them into the component type library, which contains standard descriptions of data types, modules, and interfaces that can be used to fully expose objects with ActiveX technology. Visual Basic automatically generates a type library and developers must use Explorer to set the transaction attribute.

After the component is built, Microsoft Transaction Server Explorer builds the package file to deploy the components. The package contains information about the components and roles (security) of a package. When you create a prebuilt package, the associated component files (DLLs, type libraries, and proxy-stub DLLs, if implemented) are copied to the same directory where the package file was created.

Components that make updates to multiple transactional resources (for example, database records) can ensure that their objects are always created within a transaction. If the object is created from a context (the identity of the object's creator) that has a transaction, the new object inherits that context and transaction; otherwise, the system automatically initiates a transaction for the new object. An object's context is similar in concept to the process context that an operating system maintains for an executing program. The Microsoft Transaction Server run-time environment manages a context for each object.

Components that only do a single transactional update can be declared to support, but not require, transactions. If the object is created from a context that has a transaction, the context of the new object inherits that transaction. This allows the work of multiple objects to be composed into a single atomic transaction. If the object is created from a context that doesn't have a transaction, the object can rely on the resource manager to ensure that the single update is atomic.

How Work Is Associated with a Transaction

An object has an associated context object that indicates whether the object is executing within a transaction and, if so, the identity of the transaction. Resource dispensers can use the context object to provide transaction-based services to the Transaction Server object. For example, when an object executing within a transaction allocates a database connection by using the ODBC resource dispenser, the connection is automatically enlisted on the transaction. All database updates using this connection become part of the transaction, and are either atomically committed or aborted.

Like any COM object, Transaction Server objects can maintain internal state across multiple interactions with a client. An object that has this behavior is stateful. Transaction Server objects can also be stateless, which means the object doesn't hold an intermediate state while waiting for the next call from a client.

When a transaction is either committed or aborted, all the objects involved in the transaction are deactivated, causing them to lose any state they acquired during the course of the transaction. This helps ensure transaction isolation and database consistency; it also frees server resources for use in other transactions.

Completing a transaction enables Transaction Server to deactivate an object and reclaim its resources, thus increasing the scalability of the application. Maintaining state on an object requires the object to remain activated, holding potentially valuable resources, such as database connections. Stateless objects are more efficient and are recommended.

How Objects Can Participate in Transaction Outcome

The IObjectContext interface has methods that enable a Transaction Server object to participate in determining a transaction outcome. The SetComplete, SetAbort, EnableCommit, and DisableCommit methods work in conjunction with the component transaction attribute to allow one or more objects to be simply and safely composed within transactions.

Both SetComplete and SetAbort deactivate the object on return from the method. The object is reactivated on the next call that requires object execution. Objects that need to retain state across multiple calls from a client can protect themselves from having their work committed prematurely by the client. By calling DisableCommit before returning control to the client, the object can guarantee that its transaction cannot be successfully committed without the object doing its remaining work and calling EnableCommit. These options can be set during development or when a component is brought into Explorer for registration in the Microsoft Transaction Server run-time environment.

Client-Controlled vs. Automatic Transactions

Transactions can either be controlled directly, by the client, or automatically, by the Transaction Server run-time environment.

Clients can have direct control over transactions by using a transaction context object. The client uses the ITransactionContext interface to create Transaction Server objects that execute within the client's transactions and to commit or abort the transactions.

Transactions can be automatically initiated by the Transaction Server run-time environment to satisfy the component's transaction expectations. Transaction Server components can be declared so that their objects always execute within a transaction, regardless of how the objects are created. This simplifies component development because application logic doesn't have to be written to handle the special case of an object created by a client not using transactions.

This also reduces the burden on client applications. Clients don't need to initiate a transaction simply because the component they're using requires them.

Transaction Server automatically initiates transactions as needed to satisfy a component's requirements. This occurs, for example, when a client that isn't using transactions creates an object in a Transaction Server component that's declared to require transactions.

Transaction Server completes automatic transactions when the Transaction Server object that triggered its creation has completed its work. This occurs when returning from the object after it has called SetComplete or SetAbort. The former causes the transaction to be committed; the latter causes it to be aborted.

A transaction cannot be committed while any method is executing in an object that is participating in the transaction. The system behaves as if the object disables the commit for the duration of each method call.

Good Component Design and Resource Sharing

You can build a component for use in Microsoft Transaction Server with any language capable of building COM in process. This list includes, but is not limited to:

In a server application, resource sharing is extremely important. (Imagine what would happen if 3,000 clients asked for 3,000 separate processes with 3,000 database connections.) Transaction Server manages most resource allocation by using memory allocation, component allocation, and recycling, thread pooling, and ODBC connection pooling.

A component designer must consider other potential problems as well. For example, a computer could ask for resources and hold on to them for long periods. Or, if a component does some automation to Excel, each instance could request an instance of Excel. That would mean one process per client request!

There are other things to consider. A scalable application makes use of stateless servers. Therefore, a well-designed component should be stateless. When a given client is finished with an object, the object forgets all state. When the next request comes in, all state must be gathered until the client is finished and commits the transaction.

To a desktop programmer, keeping state is efficient. In a desktop world, that is true, but in a server world, that is false. The overhead associated with keeping state is tremendous. With a single-user desktop application, keeping 256K of data is nothing, but if you have 1000 concurrent users accessing a system, 256K per client is approximately 256 MB. State should be kept only in the resource manager.

It is also important to minimize network traffic. This is a difficult thing to do. The common object-oriented approach is to have an object that has some data that we access through small, clear properties and methods. This does not work well in a server world. Every method call or property get/set is a network roundtrip that passes the minimum number of packets, filled or not. It is therefore important to minimize the number of method calls and property get/sets. Currently, the only way to do this is to use method calls that pass and receive all of the data back and forth by value. It is not enough just to use large methods. Unseen marshaling can be a performance nightmare, so do not pass pointers or object references if it is at all possible.

Transaction duration is another possible performance killer. Keep transactions as short as possible because transaction locks cause serialization and blocking.

ODBC Performance

Each time an object uses a method, it obtains, uses, and then releases its database connection. A database connection is a valuable resource. The most efficient model for resource usage in scalable applications is to use them sparingly—acquire resources only when needed, and return them as soon as possible.

Historically, acquiring resources has been an expensive operation in terms of system performance. Many programs acquire resources and hold onto them until program termination. While this strategy is effective for single-user systems, building scalable server applications requires sharing resources.

Microsoft Transaction Server provides an architecture for resource sharing through its Resource Dispenser Manager and resource dispensers. The Resource Dispenser Manager works with specific resource dispensers to automatically pool and recycle resources. The ODBC version 3.0 Driver Manager is a Microsoft Transaction Server resource dispenser, also referred to as the ODBC resource dispenser.

When you create a component, it has not implemented any Transaction Server–specific APIs. But when you run it, Transaction Server uses the ODBC resource dispenser. This happens automatically when a method uses Remote Data Objects (RDO) to access the database because RDO uses ODBC. Whenever any component running in the Transaction Server run-time environment uses ODBC directly or indirectly, the component automatically uses the ODBC resource dispenser.

When the object releases the database connection, the connection is returned to a pool. When the method is called again, it requests the same database connection. Instead of creating a new connection, the ODBC resource dispenser recycles the pooled connection, which saves time and server resources.