Although you know how to create and consume a serviced component, there's not much point to this unless you use some of the automatic services provided by COM+. In this section, you will learn how some of the key COM+ component services work and how to use them in an application. In particular, I'll discuss the following COM+ services:
Object Pooling
Just-In-Time Activation
Object Construction
Automatic Transaction Management
Queued Components
When you request to create an object on the server, the server creates a process space, instantiates the object, and performs necessary initialization. For some large and complex objects, the amount of resources consumed in creating them might be significantly high. An application might perform poorly if expensive-to-create objects are frequently created.
Wouldn't it be nice if you could maintain a pool of already created objects and then efficiently reuse them repeatedly without creating one from scratch? That's what the object pooling service of COM+ does. The object pooling service allows you to increase the scalability and performance of an application by minimizing the time and resources required in creating objects repeatedly.
You can configure a class to use the object pooling service by applying the ObjectPooling attribute on the class. Table 7.9 lists various properties of the ObjectPooling attribute that you can use to configure the way object pooling works for the class.
Property | Description |
---|---|
CreationTimeout | Specifies a length of time (in milliseconds ) to wait for an object to become available in the pool before throwing an exception. |
Enabled | Specifies whether object pooling is enabled for a component; the default value for this property is True . |
MaxPoolSize | Specifies the maximum number of pooled objects that should be created for a component. |
MinPoolSize | Specifies the minimum number of objects that should be available to a component at all times. |
In addition to using the ObjectPooling attribute, an object-pooled class also overrides the CanBePooled() method of the ServicedComponent class. The overridden version of this method should return either True or False . You'll see in the following section that an object is only pooled if the CanBePooled() method returns True .
At a conceptual level, you can envision that the COM+ places a Pooling Manager between the client and the server, as shown in Figure 7.20.
The Pooling Manager is responsible for maintaining and controlling the object pool. All client requests to the server for an object are intercepted and instead processed by the Pooling Manager. The Pooling Manager follows an internal logic as shown in Figure 7.21.
The functionality of the Pooling Manager can be summarized in the following list:
When the COM+ application is started, a MinPoolSize number of objects is created and thereafter maintained in the pool at all times when the application is running.
Each time that the pooling manager receives a request to create an object, the Pooling Manager checks to see whether the object is available in the pool. If the object is available, the Pooling Manager provides an already created object from the pool.
If no objects are currently available in the pool, Pooling Manager checks to see if the number of objects currently in the pool has reached the MaxPoolSize . If not, the Pooling Manager creates new objects to fulfill the request. The Pooling Manager tends to create as many objects as needed to keep the available objects at the level of MinPoolSize while not exceeding the MaxPoolSize .
If no object is available and no new object can be created because of the size restriction of the pool, the client requests are queued to receive the first available object from the pool. If an object cannot be made available within the time specified in the CreationTimeOut property, an exception is thrown.
When the client is done with an object, it invokes a Dispose() method on the object. The Pooling Manager intercepts this request and calls the CanBePooled() method on the object to check if the object is to be pooled. If the method returns True, the object is stored in the object pool. On the other hand, if the CanBePooled() method returns False, the object is destroyed forever.
The Pooling Manager ensures that an optimum number of objects are always available in the object pool. If the number of available objects in the pool drops below the specified minimum, new objects are created to meet any outstanding object requests and refill the pool. If the number of available objects in the pool is greater than the minimum number, those surplus objects are destroyed during a cleanup cycle.
EXAM TIP
To Pool or not to Pool? Using the object pooling service with every application might not be a good idea. Although object pooling has benefits, it also has its share of overhead. You should use object pooling in those applications in which the benefits of object pooling exceed the overheads. Some of the scenarios suitable for object pooling are
When the cost of creating an object is relatively high
When usage costs are relatively low
When an object will be reused often
When you want to limit the number of object instances
Some scenarios in which object pooling might not be useful are
When an object is inexpensive to create
When the object does not maintain any state
When the object must be activated in the caller's context
When you do not need to restrict the number of object instances
Step By Step 7.8 shows how to create an object-pooled serviced component by applying the ObjectPooling attribute and overriding the CanBePooled() method. In addition, the serviced component in Step By Step 7.8 also overrides the Activate() and DeActivate() methods of the ServicedComponent class. Recall from Table 7.2 that the Activate() method is invoked by enterprise services either when an object is created afresh or when an object is activated from the pool. The Deactivate() method is called just before an object is deactivated and returned to the pool or destroyed.
In Step By Step 7.8, I use the constructor, Activate() , and Deactivate() methods to write messages to the Windows event log. This helps you monitor how object activation and deactivation is being performed by the system.
WARNING
Don't Pool Objects with Client-Specific States When a client program repeatedly requests an instance of an object pooled serviced component, there is no guarantee that the client will receive exactly the same instance of the object that it received in the earlier requests. Any object that needs to maintain client-specific state should not be pooled.
STEP BY STEP7.8 Creating an Object-Pooled Serviced Component
|
In Step By Step 7.8, I selected the ActivationOption to be Server instead of Library because I wish to create an object in the context of the server and not that of the client.
I also registered the assembly as a COM+ application. Now when you use the Component Services administrative tool to access the properties of the NorthwindSC component in this application, you get the options to configure the object pooling parameters, as shown in Figure 7.22.
In this section, I'll demonstrate how to use an object-pooled serviced component from a client program. I'll show you two different ways to use a serviced componentthe greedy approach and a non-greedy approach. You'll understand what each of these approaches are and why you should use one over the other as you proceed. I'll first start with an example of the greedy approach to call a serviced component in Step By Step 7.9.
STEP BY STEP7.9 Using an Object-Pooled Serviced Component: Greedy Approach
|
When the form in Step By Step 7.9 is loaded, it creates an object on the server and then holds the reference to this object until the form is closed. The client is holding the resource for more time than it actually needs; therefore, I call this program a greedy client. You can also note from Step By Step 7.9 that the solution involving greedy clients does not scale well with an increasing number of clients .
An alternative to the greedy client is to create clients that are not greedy, which means that the client should allocate the server resource as late as possible and release it as early as possible. This scheme will ensure that the resources at the server are occupied only for the period of time that they are actually used. As soon as a client frees a resource, the resource can be used by another client that might be waiting for it. Step by Step 7.10 shows one such solution.
STEP BY STEP7.10 Using an Object-Pooled Serviced Component: Non-greedy Approach
|
In Step by Step 7.10, I am creating the object each time within a method call and destroying it as soon as the method is completed. This ensures that the client holds a reference to the server object only for the period when they are actually using the object.
From the perspective of conventional programming, this approach might look inefficient because if the client is repeatedly creating objects, it will be slower as compared to a client that creates an object only once and then holds onto it. However, in Step By Step 7.9 you can see that the benefits of scalability surpass the difference in speed. That is very important for enterprise applications, which must scale with an increasing number of users.
In Step By Step 7.9, it is important for the client to call the Dispose() method on server objects as soon as the objects are not needed; if the client programs don't do so, they keep holding the server resources for their lifetime.
Ideally, in this scenario, a server application should automatically dispose of server objects as soon as the client is done using them. So, what is the solution? I'll tell you about a server-side solution to deal with the dispose problem in a forthcoming section, "Just-In-Time Activation."
NOTE
Using Object Pooling to Control Licensing Using object pooling, you can set the upper limit for the consumption of a server resource. This feature of object pooling can help you scale your application as the number of licensed users increase. Moreover, because of the declarative nature of COM+ services, it is easy to configure this setting at runtime with little administrative effort.
When an object writes messages to the event log, that's a way to monitor how an object is functioning. However, you might also want to aggregate information about all objects that are currently instantiated and monitor how a component as a whole is working. In this section, you'll learn how to use the Component Services administrative tool to monitor the usage statistics for a serviced component.
STEP BY STEP7.11 Monitoring Object Statistics of a Serviced Component
|
Table 7.10 shows what each of the status view columns in the right pane of the Component Services administrative tool mean.
NOTE
Fine Tuning an Application The initial estimated settings of the MinPoolSize , MaxPoolSize , and CreationTimeOut properties might not be optimum for a specific application and its environment. Usually, administrators monitor the behavior of an application and then fine-tune its configuration settings to suit the environment. An administrator might have to experiment with several different values to reach the desired performance level. Such fine-tuning of an application can be easily performed using the Component Services administrative tool. To safely change the pool size, you should shut down the running application first.
Column | Description |
---|---|
ProgID | Identifies a specific component. |
Objects | Shows the number of objects that are currently held by the client programs. |
Activated | Shows the number of objects that are currently activated. |
Pooled | Shows the total number of objects created by the pooling manager. This number is the sum of objects that are in use and the objects that are deactivated. |
In Call | Shows the number of objects that are currently executing some client request. |
Call Time (ms) | Shows the average call duration (in milliseconds) of method calls made in the last 20 seconds (up to 20 calls). |
In Step by Step 7.11, you checked the Component Supports Events and Statistics check box to specify that you are interested in recording the statistics for a serviced component. If you want your components to always install in the COM+ catalog with this option turned on, you should apply the EventTrackingEnabled attribute to your class and set its Value property to true.
In the "Object Pooling" section, I used two different approaches for designing the client program:
In the first approach, a client creates an object and holds onto it until the client no longer needs it. The advantage of this approach is that it's faster because clients need not create objects repeatedly. The disadvantage is that this approach can be expensive in terms of server resources in a large-scale application.
In the second approach, a client can create, use, and release an object. The next time it needs the object, it creates it again. The advantage to this technique is that it conserves server resources. The disadvantage is that as your application scales up, your performance slows down. If the object is on a remote computer, each time an object is created, there must be a network round-trip, which negatively affects performance.
Although either of these approaches might be fine for a small-scale application, as your application scales up, they're both inefficient. Moreover, in an enterprise application, the server scalability should not be a factor of how the clients are designed. The just-in-time activation service of COM+ provides a server-side solution that includes advantages of both of the above approaches while avoiding the disadvantages of each.
Just-in-time activation is an automatic service provided by COM+. To use this service in a class, all you need to do is to mark the class with the JustInTimeActivation attribute set to true .
Figure 7.25 shows how the just-in-time activation service works. I have also summarized the process in the following list:
When the client requests an object from the server, COM+ intercepts that request, creates a proxy, and returns the proxy to the client. The client maintains a long-lived reference to the proxy, thinking that it is a reference to the actual object. This way, the client does not spend time repeatedly creating the object. The server also saves resources because it can delay creating the object until the client invokes a method on it.
When the client invokes a method on the object (using the proxy), COM+ actually creates the object, calls the method, return the results, and then destroys the object. Because the objects are short lived, server resources are consumed only for a small period, and the server is readily available to serve other waiting clients.
As shown in Figure 7.25, any JIT activated object will be created in its own context. The context maintains a "done bit" to specify when the object will be deactivated. The interception mechanism checks the done bit after each method call finishes. If the value of the done bit is true, the object is deactivated; otherwise , the object continues to exist. The default value of the done bit is False. Nevertheless, you can programmatically set the value of the done bit using any of the following techniques:
The ContextUtil.SetComplete() or ContextUtil.SetAbort() Methods Usually these methods are used to vote for the success or failure of a transaction, but they additionally also set the done bit to True.
The ContextUtil.DeactivateOnReturn Property When you set this property to True in a method, the property sets the done bit to True.
The AutoComplete Attribute When you always want to deactivate an object when the method call returns, you can apply this attribute to the method definition. This attribute automatically calls ContextUtil.SetComplete() if the method completes successfully. If the method throws an exception, the ContextUtil.SetAbort() method is invoked. In both cases, the done bit is set to True.
After understanding how the JIT activation works, you can appreciate how JIT activation enables scalable applications. However, the server still has a lot of work to do. If the client is frequently calling methods, the server frequently creates and destroys the object. The benefits of JIT activation are lowered when the cost of object creation is significantly high. However, you already know a COM+ service that saves the cost on object creationobject pooling. What will happen if you combine both JIT activation and object pooling services together for a serviced component? You will create a recipe for high throughput.
Throughput is a measure of the processing done by an application during a given time. Often, as the number of users increases , so does the competition for a server's resources. This normally results in an overall decrease in throughput.
The JIT activation and object pooling services complement each other's features. When these services are used in combination, they can maximize the throughput for an application by providing the following benefits:
JIT activation enables clients to hold long-lived references to a server object (through a proxy) without consuming server resources.
JIT activation enables the server objects to be destroyed as soon as their work is over in order to minimize the resource consumption on the server.
Object pooling caches already created objects and saves time by activating and deactivating the objects from the pool instead of re-creating them from scratch.
When using JIT activation in your programs, you need to consider the following points:
The lifetime of the objects is controlled by the server instead of the client. Therefore, you need not call the Dispose() method on the server object from the client. In fact, if you do so, the object will be re-created on the server so that it can be disposed of.
The server does not automatically deactivate an object. You need to set the done bit to True for COM+ to destroy an object after the current method call has completed. You can use any of the techniques mentioned in the previous section to set the done bit to True. You can also configure a method administratively to control this behavior.
The objects are created and destroyed after each method call. Therefore, you should consider the server object as stateless. JIT activation is not suitable for objects that need to maintain state across method calls.
In this section, you'll learn how to use just-in-time activation and object pooling to create an application that is more scalable and that will have improved performance. In Step By Step 7.12, you will see how a few simple changes to a serviced component enable it to scale efficiently and support a large number of clients without depending on the client to call the Dispose() method on the server objects.
STEP BY STEP7.12 Creating a JIT-Activated Serviced Component
|
In Step By Step 7.12, I have applied the JustInTimeActivation attributed to the NorthwindSC class. This attribute instructs the runtime to check the value of the done bit after each method call. The object is deactivated if the done bit evaluates to True after a method call.
I use two different ways to set the done bit in this program. In the ExecuteQuery() method, I set the DeactivateOnReturn property of the ContextUtil class to True, whereas in the UpdateData() method, I apply the AutoComplete attribute. The AutoComplete attribute always deactivates the object after the method call is completed, whereas the ContextUtil.DeactivateOnReturn property is more flexible because it can be set to True or False depending on a condition.
You can also configure the Just-In-Time Activation service using the Component Services administrative tool. To enable JIT activation for a component, you need to check the Enable Just-In-Time Activation check box in the component properties dialog box, as shown in Figure 7.26.
To set the done bit after a method completes, you need to configure a method's property, as shown in Figure 7.27.
Step By Step 7.13 demonstrates how to use a JIT-activated object-pooled serviced component. This program is similar to that of Step By Step 7.10but this time it's simpler because the server automatically takes care of disposing the server objects, and thus the client is not required to call Dispose() on the server objects.
STEP BY STEP7.13 Using a JIT-Activated Serviced Component
|
Run the project as you did in Step By Step 7.10. You'll note that results are the same despite the fact that the client program is not calling Dispose() on the server objects.
The object construction service of COM+ allows you to specify initialization information for a serviced component. The advantage of using the construction string service is that it enables the string to be configured externally using tools such as the Component Services administrative tool.
To use the object construction service in a serviced component, you need to do the following:
Apply the ConstructionEnabled Attribute You need to apply the ConstructionEnabled attribute on a class that uses the object construction service. This attribute has two properties: Enabled and Default . The default values of these properties are True and empty string, respectively. The Default property specifies the constructor string.
Override the Construct() Method To receive the currently configured constructor string from the COM+ catalog, an object must override the Construct() method of the ServicedComponent class. When the ConstructionEnabled attribute is true, the enterprise service calls this method automatically after the constructor of the component has been executed. The Construct() method receives the currently configured construction string as its only parameter.
NOTE
Using Object Construction with Object Pooling You can pair object construction with object pooling to have more control of how the clients reuse resources. For example, you can design a generic serviced component that exposes resources to the clients based on the constructor string. You can install this component several times in a COM+ application (ensuring that each component has a different CLSID) and then assign each of these components with a distinct constructor string. The COM+ application now has distinct pools of objectseach usable by a distinct groups of clients.
Once the ConstructionEnabled attribute is applied, you can configure the constructor string using the component's property sheet in the Component Services administrative tool.
REVIEW BREAK
|
GUIDED PRACTICE EXERCISE 7.2You need to modify the serviced component created in Step By Step 7.10 in such a way that system administrators should be able to configure the database connection string for the component. In order to do this, there should not be a need to recompile the application. You do not want to log events to the event log from the serviced component. How would you create such a serviced component and then configure the component using the Component Services administrative tool? This exercise helps you practice creating serviced components that use COM+ services. You should try working through this problem on your own first. If you are stuck, or if you would like to see one possible solution, follow these steps:
Guided Practice Exercise 7.2 registers the serviced component in the COM+ catalog. Before you can use the component from a client application, you should deploy the component to the GAC. The design of the client application is unaffected by the application of the ConstructionEnabled attribute. You can easily modify the client program in Step By Step 7.13 to work with this serviced component. If you have difficulty following this exercise, review the sections "Object Pooling," "Just-In-Time Activation," and "Object Construction," earlier in this chapter. After doing that review, try this exercise again. |
As discussed earlier in the section "Microsoft Transaction Server (MTS)," a transaction is a series of operations performed as a single unit. A transaction is successful only when all of the operations in that transaction succeed.
For example, consider a banking application that needs to perform a balance transfer from account A to account B for amount X. In this transaction, two atomic operations are involved. First, the balance in account A should be decreased by amount X, and second, the balance in account B should be increased by amount X. For balance transfer action to succeed, both of these atomic operations should succeed. If one of the actions fails, the system is in inconsistent state (with wrong account balances ) and the complete operation should be undone (rolled back). Instead, if both the atomic operations succeed, the balance transfer action also succeeds and the changes can be made permanent (committed).
Using transactions to ensure the reliability of applications is not a new concept. Transaction processing has been part of database management systems long before the concept of a transaction came to business objects. The transaction processing mechanism of COM+ provides two significant advantages over traditional transaction processing techniques:
Distributed Transactions A local transaction is one whose scope is limited to a single transactional resource, such as a SQL Server database. On the other hand, a distributed transaction can span over several transaction-aware resources. The transaction-aware resources in a distributed transaction might be heterogeneous (such as SQL Server database, Oracle database, Microsoft Messaging Queue) and might involve multiple computers over a network.
Automatic Transactions A typical database transaction, such as the one implemented using Transact SQL code or ADO.NET code, is manual in nature. With manual transactions, you explicitly begin and end transactions and implement all the necessary logic to take care of the commit and rollback process. However, COM+ provides automatic transaction services that you can use in your program without writing any additional code. COM+ implements this with help of a Windows service called Microsoft Distributed Transaction Coordinator (MSDTC).
In this section, I discuss how COM+ automatic transaction processing service works and how to use this service to implement transactions across a single transaction-aware resource as well as multiple transaction-aware resources residing in separate processes.
Before I go any further, let me give you a first-hand exposure on using automatic transaction processing for a local transaction in Step By Step 7.14.
In this example, I use the serviced component created in Step By Step 7.12 and demonstrate how you can use COM+ automatic transaction services without writing even a single line of additional code.
STEP BY STEP7.14 Using a Local Automatic Transaction Processing Service
|
In Step By Step 7.14, you use the Component Services administrative tool to configure the automatic transaction service for a component. Like the other COM+ services, you can also accomplish this by writing declarative attributes in your programs. You'll learn about various attributes related to transaction processing in the next few sections.
The System.Enterprise service namespace provides various classes to work with transactions in your programs. These classes hide the complexity of automatic transaction processing and provide most of the functionality with declarative attributes. Under the covers, these classes negotiate with COM+ and MSDTC services to implement the transaction.
In this section, you'll learn how these classes work together to provide automatic transaction services.
Applying the System.EnterpriseService.Transaction attribute to a class is the equivalent of enabling a transaction through the Component Services administrative tool. The benefit of applying an attribute in the program is that the component can execute in a preconfigured state right out of the box. The Transaction attribute takes a value from the TransactionOption enumeration to specify how a component participates in a transaction. The values of the TransactionOption enumeration are listed in Table 7.11.
Member | Description |
---|---|
Disabled | Specifies that the component's ability to participate with COM+ in managing transactions has been disabled. This setting is for compatibility reasons only. |
NotSupported | Specifies that the component will never participate in transactions. |
Required | Specifies that the component uses transactional resources such as databases and will participate in a transaction if one already exists; otherwise, a new transaction must be created. |
RequiresNew | Specifies that the component requires a new transaction to be created even if a transaction already exists. |
Supported | Specifies that the component will participate in a transaction if one already exists. This setting is mostly used by the components that do not themselves use any transactional resources. |
For example, ComponentA might request a new transaction to be created by using the following code:
<Transaction(TransactionOption.RequiresNew)> _ Public Class ComponentA Inherits ServicedComponent ...
When a transaction is created, it is uniquely identified by a transaction ID. Several components can share a transaction; for example, when ComponentA calls a method on ComponentB , which has been defined as follows:
<Transaction(TransactionOption.Supported)> _ Public Class ComponentA Inherits ServicedComponent ...
Then, ComponentB shares the transaction started by ComponentA . In this case, the tasks accomplished by ComponentA and ComponentB belong to the same transaction. As a result, these tasks fail or succeed together.
EXAM TIP
JIT Activation Is Required with Automatic Transaction Processing To preserve the consistency of a transaction, a component must not carry state from one transaction to another. To enforce statelessness for all transactional components, COM+ uses JIT activation. JIT activation forces an object to deactivate and lose state before the object can be activated in another transaction.
For a component, if you apply the Transaction attribute and set its value to TransactionOption.Supported , TransactionOption.Required , or TransactionOption.RequiresNew , COM+ automatically sets the JustInTimeActivation attribute to true .
Each component that participates in a transaction has its own context. The context stores various flags that specify the state of an object. Two such flags are the done bit and the consistent bit. In addition to objects, the transaction itself also has a context. The context of a transaction maintains an abort bit. The purpose of the abort bit is to determine whether the transaction as a whole failed or succeeded. I have summarized these bits and their influence on the outcome of a transaction in Table 7.12.
Bit | Scope | Description | Effect on the Transaction Outcome |
---|---|---|---|
Abort | Entire transaction | This bit is also called the doomed bit. COM+ sets this bit to False when creating a new transaction. If this bit is set to True in a transaction lifetime, it cannot be changed back. | If the abort bit is set to True , the transaction is aborted. |
Consistent | Each context | This bit is also called the happy bit. COM+ sets this bit to True when creating an object. A programmer can choose to set this bit to True or False , depending on the program logic to indicate that the object is either consistent or inconsistent. | If the consistent bit in any of the contexts is set to False , the transaction is aborted. If the consistent bit in all the contexts is set to True , the transaction is committed. |
Done | Each context | Each COM+ object that participates in a transaction must also support just-in-time activation and therefore must maintain a done bit. When a method call begins, the done bit is set to False . When a method call finishes, COM+ checks the status of the Done bit. If the bit is true, the active object is deactivated. | When exiting a method, if the done bit is set to True and the consistent bit is set to False , then the Abort bit is set to True . |
The .NET enterprise services library provides the ContextUtil class to work with the context of an object. Table 7.13 shows those methods of the ContextUtil class, which influence the done bit and the consistent bit of an object.
Method | Effect on Consistent Bit | Effect on Done Bit |
---|---|---|
DisableCommit() | False | False |
EnableCommit() | True | False |
SetAbort() | False | True |
SetComplete() | True | True |
In this section, you learn how the COM+ automatic transaction service works in a distributed scenario. Consider a scenario as shown in Figure 7.30. You have an order processing application that is divided into four layers :
Sales representatives use a Windows application to enter the orders that they receive over the telephone.
The client Windows application interacts with the ordering application for order fulfillment. The ordering application works as a service provider and interacts with other applications to fulfill an order. The main objective of this application is to keep the client from knowing how an order is processed. This scheme gives you flexibility in changing processes at the server without making any changes to the client program.
The shipping application knows how to ship an order and the billing application knows how to bill customers. These applications can reside on the same computer as the ordering application, or they might reside on different computers. If the applications are on different computers, they can communicate with the ordering application using technologies such as remoting or XML Web services.
The shipping and the billing applications maintain their own set of data independent of each other. The databases themselves might reside on different servers.
In this scenario, the need for transactions is clear. When a customer places an order, the order should be both billed and shipped as an integrated unit of work. Just billing the customer without shipping anything or vice versa is not what most organizations want to do.
Let us now see how the automatic transaction service works in this scenario. For the sake of simplicity, I assume that each of the ordering, billing, and shipping applications have just one component. The name of the component is same as the name of application.
Figure 7.31 shows how these components interact with each other to create a transaction. The process in Figure 7.31 is explained in the following steps:
When the client instantiates the ordering component, an object context is created. The done bit is set to False , whereas the consistent bit is set to True . COM+ intercepts object invocation to check whether transaction services are needed. The ordering component has a Transaction attribute set to the TransactionOption.RequiresNew value. COM+ creates a new transaction and sets the abort bit to False . The ordering component is designated as the root object of the transaction. A root object coordinates with all other objects in a transaction. The client calls a method on the ordering component.
When the ordering component instantiates the billing component, COM+ intercepts to check whether transaction services are needed. The billing component has a Transaction attribute set to the TransactionOption.Supported value. COM+ determines that the billing component can support the transaction started by ordering component and extends the scope of the transaction to cover the billing component. The context of the billing object is initialized with the consistent bit set to True and the done bit set to True .
NOTE
MSDTC and Two-phase Commit In a transaction, MSDTC works in two phases. The first phase is the prepare phase in which MSDTC interacts with resource managers for resources such as databases, message queues, and so on and asks them to record new and old values in a durable place. Based on the resource manager's feedback, MSDTC determines the success or failure of an operation.
The second phase is the commit phase. In this phase, MSDTC asks all the individual resource managers to perform a commit operation on their resources. MSDTC collects the votes ; if all the commit operations are successful, MSDTC instructs all resource managers to make the changes permanent. If any of the commits failed, MSDTC instructs all the resource managers to roll back their operation based on the information that they collected in the prepare phase.
The ordering component calls a method on the billing component. The billing object interacts with a SQL Server database to update a table of confirmed shipments. COM+ uses MSDTC to record any changes made to the database so that the changes can be rolled back at a later stage. If the update is successful, the consistent bit is set to True , but if there were any errors, the consistent bit is set to False . If the billing object wants to deactivate itself after the method call, it sets the done bit to True ; otherwise, the done bit remains set to False . If the done bit is True and the consistent bit is False , the abort bit of the transaction is set to True and the control transfers to step 6.
When the method returns from the billing object, COM+ intercepts the call and records the status of the consistent bit. If the done bit is True , the billing object is deactivated.
The control comes back to the ordering object. The ordering component now repeats steps 24 to instantiate the shipping component and invoke a method on its object.
The control comes to the ordering object. COM+ checks the status of the abort bit. If the abort bit is True , the entire transaction is aborted and COM+ requests DTC to roll back any changes that were made to the databases. If the abort bit is False , the status of all the consistent bits is checked. If any of these bits is False , the transaction is aborted and rollback is performed. If all the consistent bits are True , COM+ requests DTC to finally commit all the changes to the databases.
Finally, the root object sets its done bit to True and returns from the method that was invoked by the client. COM+ intercepts the call to deactivate the root object and destroys the transaction. The control is transferred to the client.
In the preceding steps, if you programmatically want to control the success or failure of an operation, you can do so by calling the methods of the ContextUtil class (see Table 7.5). However, a common choice is to apply the AutoComplete attribute on the method call. This attribute automatically calls ContextUtil.SetComplete() if the methods complete successfully. Otherwise, ContextUtil.SetAbort() is called to abort the transaction.
Now that you are familiar with how transactions work, it's time to write a program that makes use of automatic transaction services in a distributed application. In this section, I'll use the distributed ordering application discussed previously and write a shipping component, a billing component, an ordering component, and a client application.
Step By Step 7.15 shows how to create a billing component that supports transactions and updates a database table with billing records.
STEP BY STEP7.15 Using Distributed Transactions: Creating a Shipping Component
|
Note that in Step By Step 7.15, I used the TransactionOption.Supported option because the shipping component is not invoked on its own. Instead, this component is invoked by the ordering application as part of the order fulfillment process, and so will join the preexisting transaction.
Step By Step 7.16 shows how to create a billing component that supports transactions and updates a database table with billing records.
STEP BY STEP7.16 Using Distributed Transactions: Creating a Billing Component
|
Note that in Step By Step 7.16, I used the TransactionOption.Supported option because the Billing component is not invoked on its own. Instead, this component is invoked by the ordering component as part of the order fulfillment process. Step By Step 7.17 shows how to create the ordering component.
STEP BY STEP7.17 Using Distributed Transactions: Creating an Ordering Component
|
The ordering component needs to work as a root object for a transaction. For this reason, the Transaction attribute in Step By Step 7.17 uses the value TransactionOption.RequiresNew .
Now you have all the server-side components ready. In Step By Step 7.18, I'll create a client application that calls the ordering component. I have created the client application as a Windows application; however, creating the client program as a Web application is not much different.
STEP BY STEP7.18 Using Distributed Transactions: Creating a Client Order Form
|
The client application created in Step By Step 7.18 does not itself take part in the transaction, but various server components that the client application uses do take part in a transaction. Automatic transaction processing increases the reliability of applications without putting a lot of pain in programming.
In the last step of Step By Step 7.18, I simulated a scenario that an enterprise application should always be ready to deal with. This is a scenario in which one or more of an application's components are unavailable. When the Shipping component was unavailable, you were not able to record orders in the system. For many applications, availability is the number one priority. Availability becomes a major challenge when applications are distributed because the points of failure are now increased and some are out of your control.
In the next section, I'll discuss how another COM+ servicequeued components solves the problem of availability.
From the perspective of a client, a q ueued component is a serviced component that can be invoked and executed asynchronously. The queued components are based on the Microsoft Message Queuing (MSMQ) technology, which is part of the Windows operating system.
Communication between a client and a queued component involves four basic components between the client and server, as shown in Figure 7.33. These components are
Recorder The recorder kicks in when a client makes a call to the queued component. The recorder records the call, packages it as a message, and stores the message in the message queue.
Queue The queue is a repository of messages. Each COM+ application has a set of queues assigned to it. There is one primary queue, five retry queues, and one dead-letter queue. When the message arrives from the recorder, the message waits in the primary queue to be picked up by the queued component. If there is an error in processing, the message is sent to the first retry queue; if the message processing fails in the first retry, the message is moved to the second retry queue; and so on. The retry queues differ from each other by the frequency with which they retry a message. The first retry queue is the most frequent, whereas the fifth one is slowest. If there is an error-processing message in the fifth queue, the message is finally moved to a dead-letter queue where no further retries are made. Occasionally, you might want to check the dead-letter queue and custom process any failed message.
Listener The listener's role is to poll the queue for incoming messages and when there is one to pass the message to the player.
Player The player unpacks a message and invokes the methods that were recorded by the client on the queued component.
Creating a queued component is just like creating any other serviced component. To configure a component to work as a queued component, you need to apply the following two attributes:
ApplicationQueuing Attribute You apply this attribute at the assembly level to enable queuing support for an application. If a client will call queued components, the QueueListenerEnabled property must be set to true.
<Assembly: ApplicationQueuing(_ Enabled:=true, QueueListenerEnabled:=true)>
InterfaceQueueing Attribute You apply this attribute at the component level to specify the interface through which COM+ allows the calls on the component to be recorded and stored in the queue. For example,
<InterfaceQueuing(Enabled:=true, _ Interface:="IOrdering")>
The execution lifetime of a queued component and the client application might be different. Therefore, when creating a queued component, you must implement the following guidelines:
Methods should not return any value or reference parameters.
All calls made by a client should be self sufficient. The queued component has no way to generate a callback to the client program if more information is needed.
Methods should not throw any application-specific exceptions because the client might not be available to respond to the exceptions.
Step By Step 7.19 shows how to use a queued component that is capable of listening to a message queue. When a message arrives, the component receives orders and calls other components to process them.
STEP BY STEP7.19 Using Queued Components: Creating an Ordering Component
|
After you have registered a serviced component in the COM+ catalog, as you did in Step By Step 7.19, access the properties window of the COM+ application via the Component Services administrative tool. In the Queuing tab, you see that the options for queuing and listening are already configured, as shown in Figure 7.34.
You can also configure an interface to support queuing through its property window, as shown in Figure 7.35.
One of the ways you create an object in the client program is by using the new operator. However, you don't want to use that when working with queue because with a queued component, your objective is not to create an instance of an object. Instead, you need a way in which you can record a message for the client and store it in a queue so that the server object can read that message when possible.
Recording a message for a queued component is generally a three-step process:
Call the Marshal.BindToMoniker() method and pass it a moniker string that corresponds to the interface of the queued component. A moniker string is formed by preceding the full type name (qualified with namespace) with the string "queue:/new:" . For example,
Marshal.BindToMoniker("queue:/new:StepByStep7_19. Ordering")
The Marshal.BindToMoniker() method returns a reference to the interface identified by the given moniker string.
Use the interface reference obtained in step 1 to execute methods on the queued component. These methods are not executed immediately; instead, they will be recorded and placed in the message queue.
When done calling methods, call the Marshal.ReleaseComObject() method to release the reference of the interface reference obtained in step 1.
Step By Step 7.20 shows how to create a client program that uses the Marshal.BindToMoniker() method to record messages for a queued component.
STEP BY STEP7.20 Using Queued Components: Creating a Client Order Form
|
In Step By Step 7.20, you learned how to record messages in a queue for a serviced component. You also experimented with making an application available irrespective of failure of one or more components.
REVIEW BREAK
|
Top |