Microsoft Transaction Server

Ed Musters

Listen up, C++ developers! The release of Microsoft Transaction Server is the most important advance for software developers in recent years. Beats me why Microsoft seems to be downplaying this product, but get involved, because it will change the way you program-guaranteed.

Microsoft Transaction Server (MTS) is a substantial product, even in its first release. In this article, I'll introduce you to the basics. Perhaps the greatest challenge facing the developer in working with this product effectively is the background knowledge that's required. If you're comfortable creating ActiveX components, you're on your way! If not, invest the time to come fully up to speed on object-oriented design and programming and to thoroughly understand DCOM programming in the Microsoft Visual C++ world.

Transaction Server structure and benefits

According to Microsoft, Transaction Server "makes it easier to create high-performance, scalable, and robust Enterprise and Internet applications." It provides the foundation for building distributed component-based (DCOM, a.k.a. ActiveX) applications and is thus specifically suited to the "middle-tier," or the Business Service, layer. You, the developer, write a single-user ActiveX component, focusing on the Business Application logic you need to write. You don't have to worry about resource acquisition, queuing, pooling, multi-threading, connections, context, security, or transactional commit/backout-you let Transaction Server worry about that. I can practically hear the cheers from the multitudes of C++ programmers who have braved the world of scalable distributable application development on Windows NT.

Is this really possible? Is it really that easy? The answers are yes and yes. Figure 1 shows the structure of MTS. You write a single-user DCOM object (as a DLL) and "install" it into MTS-by drag and drop, no less! Transaction Server runs under Windows NT 4.0 and allows Thin (Inter/Intranet) clients via Active Server Pages, or "Smart" (Client/Server, or a politically correct term for Fat) clients to transparently create and use these objects wherever they're installed. The "magic" is done through Registry redirection, a topic I'll save for a more advanced article. There's still the matter of needing to use the Transaction Server API, but this article will demonstrate how simple that is compared to the horrific alternative of managing all that pooling and multi-threading stuff.

Figure 1. Structure of Microsoft Transaction Server.

The remainder of the diagram presented in Figure 1 demonstrates that Transaction Server has pools of resources at its disposal, the most notable being a pool of database connections to SQL Server. In a very-near-future release of MTS, all XA-compliant resources/databases will also be supported. With the release of the Cedar technology, even CICS transactions running under MVS on mainframes can be treated as components over the SNA Server LU 6.2 protocol, fully participating in transactional commit and backout.

This technology is worth paying attention to! With the release of NT 5.0, Transaction Server will no longer be a product, but part of the operating system services. This means that the infrastructure for distributed transactional components will be built into the O/S. Certainly, this is a quantum leap forward for Developer-kind, and a world that I, for one, plan to be living in.

MTS Explorer

Like all other Microsoft BackOffice products, MTS has a very nice Explorer administration metaphor, shown in Figure 2. Briefly, logical groups of components are grouped into packages-a rough equivalent to an application. A package can be easily exported from one copy of MTS to another, making installation and distribution of your packages very simple. One can drill down on the components to view the interfaces. When components are activated in MTS, the transaction server balls start spinning, and you can monitor current levels of component instantiation and summary levels of transaction activity.

Figure 2. The Microsoft Transaction Server.

The MoveMoney and Account components have each been written in three languages-Visual Basic, Visual C++, and Visual J++. This is provided for comparison purposes, which you'll soon see. The point is that an ActiveX component is an ActiveX component. It no longer matters what language a developer uses to create the component; choose the best language to suit your preference or the purpose at hand.

It's all about transactions

For the remainder of this article, I'll be using the Sample Bank application that comes as a demo when you install MTS. I'll show in some detail the most common of all transaction scenarios-the funds transfer, shown in Figure 3. Money is taken from Account 2 and placed into Account 1. But what if the Credit component is on Machine A and the Debit component is on Machine B? What if one component fails? Did the account update take place or not? How do I physically track and back out of the mess created by even this very simple scenario?

Figure 3. Diagram of the Sample Bank application.

Fortunately, MTS comes to the rescue by providing transaction management for "free." Transactional units are carried out as "all-or-nothing" propositions-if any part of the transaction fails, such as the account debit, then MTS will back out the work of all other components. This magic is possible through the wonders of the Distributed Transaction Co-ordinator (DTC). DTC originally shipped with SQL Server 6.5 to transparently handle the two-phase commit of distributed database transactions.

The MoveMoney component performs the funds transfer, and the Account component handles the updating of account information on the SQL Server database. Note how the transactional units match up to the Transaction column in Figure 2. If we right-click on the MoveMoney component and choose Properties, we'll get a tabbed Property sheet.

MoveMoney and Account are required to participate in a transaction; these two components will succeed or fail together. The UpdateReceipt component requires a new transaction, and this component's success or failure will have no effect on the funds transfer transaction. The GetReceipt component doesn't participate in a transaction, which implies that the component manages no durable or permanent data. The last option-"Supports Transaction"-implies that the component isn't required to be in a transaction but will participate if requested to do so.

It's all about simplicity

MTS offers the developer amazing levels of simplicity that deliver components with complex multi-user and scalable features. The next major area of simplicity afforded is component security that integrates tightly with NT Security. MTS uses the concept of roles-you add NT users to global groups under Windows NT Server, then assign the groups of users to roles that can be queried through the Transaction Server API. For example, a component can ensure that funds transfers of more than $500 can only be performed by a user in a "Managers" role.

The concept of an Activity, which is merely a single logical thread of execution, even across multiple machines, provides for thread safety. All of the horrifyingly complex issues regarding multi-threading are handled for you entirely by MTS.

The ideal Transaction Server component lives and dies as quickly as possible. It acquires resources as late as possible, and completes (thus releasing resources) as soon as possible. Because pools of objects are instantiated and ready for use, the process of creating and destroying components is inexpensive-it merely requires picking up an existing object for use from the pool. In fact, a client can hold a reference to an object (the object context) as long as desired, while MTS has in fact released the object and other resources in the background. This illustrates a concept known as Just In Time (JIT) activation.

Traditionally, SQL Server database connections were very "expensive" to obtain. Once acquired, a connection would be held as long as possible, often for the lifetime of the client application! Now you can create and destroy database connections with reckless abandon, because MTS treats database connections as "resources" and pools these as well. Requested database connections merely come from the existing pool.

Microsoft Transaction Server API basics

The Sample Bank application client user interface is shown in Figure 4. The client is set up to exercise the components. Figure 4 shows the client being asked to invoke the C++ MoveMoney component under Transaction Server a total of 10 times, transferring $25 from Account 2 to Account 1 each time.

Figure 4. The Sample Bank Application.

Now, let's consider some C++ code that will illustrate how easy it really is to tap into the power of MTS. The following sample code demonstrates Transaction Server API basics, using the MoveMoney component of the Sample Bank application. You can get this code yourself, as described in the "Further Information" sidebar.

First, acquire an object context, which is the key to every Transaction Server transaction.

// get our object context
   hr = GetObjectContext(&pObjectContext);
   if (!SUCCEEDED(hr)) goto Error;

Next, verify that if the transfer amount is over $500, then the "Managers" role is required to perform the action. Listing 1 shows that check. [Note: In code, an underscore (_) as the last character of a line indicates that the line has been broken for layout purposes. To run the code in Visual C++, you must recombine the broken line into a single line.-Ed.]

Listing 1. Checking that a Manager is handling large transfers.

if (lAmount > 500 || lAmount < -500) {
      BOOL bInRole;
      BSTR bstrRole;
      bstrRole = SysAllocString(L"Managers");
      hr = pObjectContext->IsCallerInRole(bstrRole, 
                                &bInRole);
      SysFreeString(bstrRole);
      if (!SUCCEEDED(hr)) goto Error;
      if (!bInRole) {
         *pbstrResult = SysAllocString(L"Need _
         'Managers' role for amounts over $500");
         goto Error;
      }
   }

Next, create an Account object and perform the Credit and Debit actions to complete the transfer. Note that this creates an object instance within the context of the MoveMoney component. That is, the Account will participate in the same transaction as the MoveMoney component.

// create the account object using our context
   hr = pObjectContext->CreateInstance(CLSID_CAccount,
      IID_IAccount, (void**)&pAccount);
   if (!SUCCEEDED(hr)) goto Error;

   // call the post function based on 
   //the transaction type
   .
   .
   else if (wcscmp(bstrTranType, L"Transfer") == 0) {
       BSTR bstrResult1, bstrResult2;
      // do the credit
      hr = pAccount->Post(lSecondAccount, lAmount, 
                          &bstrResult1, plRetVal);
      if (!SUCCEEDED(hr) || *plRetVal != 0) {
         *pbstrResult = bstrResult1;
         goto Error;
      }
      // then do the debit
      hr = pAccount->Post(lPrimeAccount, 0 - lAmount, 
                          &bstrResult2, plRetVal);
      if (!SUCCEEDED(hr) || *plRetVal != 0) {
         *pbstrResult = bstrResult2;
         goto Error;
      }
      .
      .
   }

If all operations completed successfully, simply call SetComplete, and you're finished!

pObjectContext->SetComplete();   

The error handling is equally simple. Make a call to SetAbort, and MTS will take care of backing out any transactions in progress and cleaning up the mess!

Error:
   pObjectContext->SetAbort();  

ODBC SQL Server access

Let's take a closer look at the Account component, which is responsible for updating the Account information stored in SQL Server. You can see in Listing 2 that this is all extremely straightforward ODBC API database access stuff.

Listing 2. Updating the Account information.

// get our object context
   IObjectContext* pObjectContext = NULL;
   hr = GetObjectContext(&pObjectContext);
   if (!SUCCEEDED(hr) || pObjectContext == NULL) 
      goto Error;

   // obtain the ODBC environment and connection
   rc = SQLAllocEnv(&henv);
   if (!SQLSUCCEEDED(rc)) goto Error;
   
   rc = SQLAllocConnect(henv, &hdbc);
   if (!SQLSUCCEEDED(rc)) goto Error;

   rc = SQLConnect(hdbc,
      (unsigned char*)"MTxSamples", SQL_NTS,
      (unsigned char*)"sa", SQL_NTS, 
      (unsigned char*)"", SQL_NTS);
   if (!SQLSUCCEEDED(rc)) goto Error;

   rc = SQLAllocStmt(hdbc, &hstmt);
   if (!SQLSUCCEEDED(rc)) goto Error;

   // update the balance
   sprintf(szSqlStmt,
      (const char*)"UPDATE Account SET Balance = _
      Balance + %ld WHERE AccountNo = %ld",
      lAmount, lAccount);
   rc = SQLExecDirect(hstmt, 
           (unsigned char*)szSqlStmt, SQL_NTS);
   if (!SQLSUCCEEDED(rc)) goto Error;

   // get resulting balance, which may have been 
   // further updated via triggers
   sprintf(szSqlStmt,
      (const char*)
"SELECT Balance FROM Account WHERE AccountNo = %ld",
      lAccount);
   rc = SQLExecDirect(hstmt,(unsigned char*)szSqlStmt,
                     SQL_NTS);
   if (!SQLSUCCEEDED(rc)) goto Error;

   long lBalance;
   rc = SQLBindCol(hstmt, 1, SQL_C_DEFAULT, &lBalance,
                   0, NULL);
   if (!SQLSUCCEEDED(rc)) goto Error;

   rc = SQLFetch(hstmt);
   if (!SQLSUCCEEDED(rc)) goto Error;

    if (lAmount < 0 && lBalance < 0) {
      swprintf(*pbstrResult, 
   L"Error. Account %ld would be overdrawn (%ld).",
         lAccount, lBalance);
      goto Error;
   }
   else {
      swprintf(*pbstrResult, 
          L"%s account %ld, balance is $%ld. (VC++)",
      ((lAmount >= 0) ? L"Credit to" : L"Debit from"),
         lAccount, lBalance);
   }
   
   pObjectContext->SetComplete();   

Done! No transaction logic embedded in the code. No commits, no rollbacks. MTS and DTC take care of that for you. If you ever run into any trouble in an MTS component, you can just "bail out" by going to Error, which in turn calls SetAbort and lets MTS do the cleanup! This includes the condition of backing out of an overdrawn account situation. This is about as easy as server-side coding gets. This is extremely cool. s

Ed Musters, MCSD, is a partner at Sage Information Consultants, Inc. He's proud to be the only Canadian listed on the VC++ Experts page on the Microsoft site. Visit the Sage Web page at http://www.sageconsultants.com. emusters@sageconsultants.com.