Component Design Principles For MTS

We don’t actually have to do anything to a component to use it in MTS, other than install it in a Package—as you'll see in a while. The component will benefit from the fact that MTS will run it in its own memory space separate from the Web server, and MTS will also control activation and deactivate to make it available to applications more quickly.

However, to feel the real benefits of MTS, we need to adapt our components and applications by adding MTS-specific code to them. This is easy enough to do, involving generally only a few lines. There are some rules that we need to abide by to get the best performance from MTS, and there are also two different MTS interface objects that we can work with.

Getting the Best Performance From MTS

The three main points to remember when you create applications or components for use in MTS are:

Using MTS Interface Objects In A Component

There are two main COM interface objects available in MTS that we can code to, and between them they allow us to control how our applications and components behave within the MTS environment.

The ObjectContext Interface Object

The most useful interface the MTS provides for our components is that of the context object that is allocated to our application for this component. Every context object exposes the COM interface IObjectContext. There is a global MTS method GetObjectContext() that returns a reference (or interface pointer) to the context object. In a Visual Basic component, we normally use this as follows:

Dim objOContext As ObjectContext 
Set objOContext = GetObjectContext()

Then we can use the methods of the context object to:

We'll be looking at the whole subject of security in a later chapter. For the meantime, we'll concentrate on how we use the other methods within our components. The SetComplete and SetAbort methods, as we've seen, are used to indicate to MTS whether our component is happy with the outcome of its operations, for example:

objOContext.SetComplete

tells MTS that we have completed all the operations we intended, and that we are happy that everything went well. As far as this component is concerned, the complete transaction can be committed. If something went wrong, however, we call:

objOContext.SetAbort

This tells MTS that we aren't happy with the outcome of the operations we performed, and that it is to abort the entire transaction and roll back all the changes made by all the other components within this transaction.

If we are embarking on a series of operations, which at certain points could leave data in an indeterminate state, we can tell MTS that the current transaction is not to be committed under any circumstances until we're ready. To do this we with:

objOContext.DisableCommit

Then, when we're ready we can call:

objOContext.EnableCommit

to indicate that MTS can commit the transaction, but should not release and reuse this object (if we called SetComplete, MTS would destroy our object instance).

Whether the transaction is committed will depend, of course, on whether all the components within the transaction (including ours) have signaled that they are also happy to commit as well.

Creating Component Instances

The context object also provides the CreateInstance method, which we can use to create dependent instances of other objects, for use by our component. This doesn't stop us using the New or CreateObject methods, but it's important to understand the effects of each one as far as MTS is concerned.

The New keyword in VB is used to create a new instance of a class as an object, for use within the current application. When used in a component within MTS, it creates a Private instance of an object that MTS knows nothing about. It won't have its own context object, and won't be included within the current transaction. In other words, it has to look after itself with no help from MTS.

The CreateObject method is almost exactly the opposite. When used in a component running within MTS, this creates a new instance of an object that MTS will treat as separate from the current component instance—and it will get its own context object. However, this will not contain any information from the context of the component that created the new instance, so it will run outside the current transaction.

The CreateInstance method of the context object provides a solution to these two problems. It creates a new instance of the referenced component and provides it with a new context object. However, it also copies the transaction information from the context of the object that created it into the new context—thus making it part of the current transaction:

objOContext.CreateInstance("MyClasses.Customer")

The ObjectControl Interface Object

The second COM interface, named IObjectControl, is that of the component class factory, as stored in MTS when the component is installed. This is useful for carrying out tasks during activation and deactivation of the component. In Visual Basic, we implement this interface within our component using the Implements keyword:

Implements ObjectControl

Our ObjectControl interface must provide three methods, Activate, Deactivate and CanBePooled. Together they provide a way for us to control how our object interacts with MTS. The Activate method is called when an instance of our component is created—either for the first time or from the cached object pool if it has been deactivated after use. The Deactivate method is called just before it is returned to the cache after use. The CanBePooled method is used to tell MTS whether the component instance can be pooled or not; though remember that instance pooling is not currently supported—only a limited form of component caching is implemented at present.

Whether we implement it is entirely optional, but if we do need to carry out any processing when the component is activated or deactivated, we have to implement it so that MTS will provide the Activate and Deactivate events.

To see how we can use the ObjectControl interface, look at the following code. It acquires a reference to the context object as soon as the component instance is activated, and tells MTS that it will be OK to pool this instance if Microsoft ever gets round to implementing support for it:

Implements ObjectControl
Private objOContext    'global variable to hold the object context

Private Function ObjectControl_Activate()
  'get the object context as soon as instance is activated
  Set objOContext = GetObjectContext()
End Function

Private Function ObjectControl_Deactivate()
  'release the object context
  Set objOContext = Nothing
End Function

Private Function ObjectControl_CanBePooled()
  ObjectControl_CanBePooled = True
End Function

Of course, we can do any other initialization and clean-up we need to in the Activate and Deactivate methods. If the object needs to maintain state for any reason (though it generally shouldn't) we can save and reload values here. We can also use them to create and destroy instances of any other objects we need to use.

Implements ObjectControl
Private objOContext    'global variable to hold the object context
Private objCustomer    'global variable to hold customer object

Private Function ObjectControl_Activate()
  'get the object context as soon as instance is activated
  Set objOContext = GetObjectContext()
  Set objCustomer = objOContext.CreateInstance("MyClasses.Customer")
  objCustomer.LoadValues  'custom routine to load state from disk
End Function

Private Function ObjectControl_Deactivate()
  'release the object context
  objCustomer.SaveValues  'custom routine to save state to disk
  Set objCustomer = Nothing
  Set objOContext = Nothing
End Function

Starting a Transaction

The example component we've been using so far, and which we'll continue to use in this chapter, is a stand-alone server component. It doesn't require a transaction, because only this component is used in the application. We're really only using MTS to get the benefit of its component instance management features. However, this isn't usually the case—we'll often have two or more components in use and these need to be part of a transaction.

Component Transaction Support Options

MTS allocates a Transaction Support property to each component installed within it. This property has four possible settings, and defines how the component will behave within MTS when activated. The options are:

Requires a transaction The component will run within an existing transaction if one already exists. If not MTS will start a new one.
Requires a new transaction MTS will start a new transaction each time an instance of the component is activated.
Supports transactions The component will run within an existing transaction if one exists. If not, it will run without a transaction.
Does not support transactions The component will always run outside any existing transactions.

So, we could set the Transaction Support property for one component (let's call it A) to 'Requires a new transaction' and the other (B) to 'Requires a transaction' (or even 'Supports transactions'). The only thing now is that we would have to be sure to always instantiate them in the right order. If we instantiate A after B, it won’t be part of the same transaction as B—it will create its own new one. And if we had set B to just 'Supports transactions', we wouldn't get a transaction for this one at all unless the application had already started one for a different component.

On top of this, we also reduce the opportunities for reusing components if we have to run them in a particular order. We can’t use two components that have the 'Requires a new transaction' setting inside one transaction. What we need to do in this case is set all the components that actually achieve a task to 'Requires a transaction' or 'Supports transactions', so that we can freely use them together in any combination.

Using A Parent Component To Control A Transaction

The usual way to get round this problem of the Transaction Setting property is to have a 'parent' component that creates instances of the other components and manages them. In the case of the Wrox Car Co application, which you'll be seeing a lot more of in the following chapter, placing an order involves two components. One handles local database updates, and the other handles remote database updates:

The parent component is responsible for initiating a transaction that includes the other two components. For this reason, the parent component has its Transaction Setting property set to 'Requires a new transaction' while the other two components, which do the real work, have their Transaction Setting properties set to 'Requires a transaction'.

SetComplete And SetAbort In A Parent Component

The 'parent' component is part of the same transaction as any components that it creates using the CreateInstance method of the ObjectContext object. (Remember that this is not the case with New or CreateObject). Therefore it can control the outcome of the transaction using the SetAbort and SetComplete methods in just the same way as the components it creates can.

This isn’t usual, however, because the components that do the work should indicate success or failure to MTS by calling SetComplete or SetAbort as appropriate. The only circumstances that the parent component might use these methods are if it has to cancel the transaction on command for some reason. However, it can use the DisableCommit and EnableCommit methods to control when MTS can make the transaction changes permanent, as we saw earlier. This might be useful in a situation where the data could be in an inconsistent state during certain parts of the process.

Nested Transactions

We've seen how MTS automatically enlists components into an existing transaction when they are created with the CreateInstance method of the parent's ObjectContext object. However, this only works if the component either supports transactions or requires a transaction. If it requires a new transaction, it cannot be enlisted into an existing one.

In this case, MTS starts a new nested transaction for this component and any other components it enlists (unless they too require a new transaction). It is then up to this component to indicate to MTS whether it wants to commit or abort the original transaction (by calling SetComplete or SetAbort) based on the results of the nested transaction.

For example, the following diagram shows four components A, B, C and D, together with their transaction support property. RT means that this component requires (an existing) transaction or supports transactions, while RNT means that the component requires a new transaction:

In this case a single automatic transaction encompasses all the components, and will only complete if all four components agree to commit. However if component C was marked as requiring a new transaction, as in the next diagram, a nested transaction is created. Now component C can still commit the transaction (by calling SetComplete) even if component D failed—if this was appropriate.

Using ASP To Start A Transaction

We generally think of components as being compiled DLLs created in languages like C++, J++, Delphi or Visual Basic. However Active Server Pages scripts can also be considered as server-side components, and ASP is an ideal tool for initiating and controlling a transaction within MTS. We can take advantage of the ObjectContext interface in Active Server Pages just as we can in other components.

In Active Server Pages (under IIS 4) we use a different technique to reference the MTS context object. We add a line to the start of the ASP page that indicates how we want to use components within MTS in this page:

<%@ Transaction = value %>

Where value can be:

Required Indicates that MTS should either use the current transaction for this page, or start a new transaction if one is not already available.
Requires_New Indicates that MTS should start a new transaction even if there is an existing one.
Supported Indicates that MTS should use the current transaction if one exists, but not start a new one.
Not_Supported Indicates that this page is not to be included in a transaction.

These values are, of course, similar to the Transaction Property settings we listed earlier for compiled components.

Note that ASP only allows one '@' line in a page. If you already use this to set the default script language for the page, you can add the Transaction statement to the same line:

<%@ Language = VBScript Transaction = Required %>

Using The ObjectContext Events in ASP

MTS also provides our ASP scripts with events. This gives us an easy way to discover the outcome of a single component's execution, or the result of a multi-component transaction. Remember that components can initiate processes that occur concurrently as part of a complete transaction. So, simply calling the methods of each component within a transaction may not provide confirmation that they all completed properly unless you design the components specifically to do this.

All we have to do is create an event handler for the OnTransactionCommit() and OnTransactionAbort() events that MTS provides:

Sub OnTransactionCommit()
   'code for when transaction completed OK
End
Sub OnTransactionAbort()
   'code for when transaction failed
End

We'll see how these can be used when we adapt and use the WCCFinance sample component in MTS in the next section of this chapter.

© 1998 by Wrox Press. All rights reserved.