Compensating Resource Managers
Windows 2000 introduces a new set of component services (COM+ Services) that extend the ability to create rich distributed transactional solutions for your Windows or web applications. Visual FoxPro 7.0 includes new features to improve its COM Server story to better support many of these great services. These features include the ability to implement an interface as well as reference objects that don’t necessarily have an IDispatch interface.
Some of the COM+ Services such as Transactions (formerly known as Microsoft Transaction Server) existed prior to Windows 2000 and were supported by Visual FoxPro. New services such as COM+ Events and Queued Components offer new options for your applications that weren’t previously available in Visual FoxPro. For more information on COM+ Services, see the COM+ (Components Services) section of the Platform SDK http://msdn.microsoft.com/developer/sdk/platform.asp.
The samples below are very simple and merely meant to show basic concepts of using a specific service. Use of these services in real world applications will typically require better data validation and error handling. You should be familiar with the basics of using COM+ Applications such as creating a new application (formerly known as MTS package), adding new components, and setting transaction attributes. Instructional details on using COM+ Services are available in the Platform SDK.
Microsoft Transaction Server introduced a great story for building distributed transactional applications. With VFP6 SP3, you could create highly scalable applications using VFP MTDLL servers. This example shows transactions using a VFP database containing remote views to SQL Server. Because SQL Server data supports OLE Transactions, its actions are processed by the Microsoft Distributed Transaction Coordinator (MS DTC). So, all data inserts, updates and deletes will automatically be rolled back if the transaction is aborted. Since VFP data does not support OLE Transactions, its data is not automatically rolled back with an aborted transaction. However, you can use Compensating Resource Managers (see below) to achieve the same outcome. This example also includes a component that sends a message to an MSMQ queue. Since MSMQ queues can be transactional via the MS DTC, messages sent to a queue will be removed if the transaction is aborted.
The example is fairly complex in that it uses 4 VFP COM Components (OLEPUBLICs). The ORDER component is actually the first one called and it initiates the transaction. The first component it calls is the MSMQ one (if setup). A message is sent to an MSMQ Queue containing the details of the order. When the MSMQ component is finished, it calls SetComplete() to commit its part of the transaction and deactivate itself. The ORDER component next calls the CUSTOMER component. If the customer is valid (maxordamt > $5000) then the order amount is debited from account and SetComplete() is called. If invalid customer, then SetAbort() is called and entire transaction including MSMQ message is rolled back. Finally, if the order is still intact, the ORDER component calls the PRODUCT component to debit quantity amounts. Again, if not enough quantity of an item is in stock, the entire transaction is rolled back including the MSMQ message and any PRODUCT data edits.
One of the enhancements added to transaction support in the new COM+ Services is the ability to separate the transaction and activation functionality provided by SetComplete()/SetAbort(). With MTS, calling SetComplete() not only committed your transaction, it also deactivated the component. There are many scenarios, however, where you would want to set a particular transaction state in a method calls of an object and possibly change this in a subsequent method call. With MTS, you needed to provide fancy error trapping sorts of routines to accomplish such behavior. COM+ now provides a new interface called IContextState which allows for more granular control of Consistency and Doneness. The following code snippet can be used in your COM+ Applications to obtain reference to the ContextState object and make these settings. Notice the use of new GetInterface() function.
LOCAL oMTX, oContext, oContextState
LOCAL lTxnState, lGetTxnState, lDone,
lGetDone
lGetDone = .F. && initialize setting
lGetTxnState = 0 && initialize setting
oMTX = CREATEOBJECT("MTXAS.APPSERVER.1")
oContext = oMTX.GetObjectContext()
oContextState =
GetInterface(oContext,"IContextState")
* Handle activation setting (Doneness)
* Values: .T. - Deactivate, .F. - Leave
activated
lDone = .T.
oContextState.SetDeactivateOnReturn(lDone)
oContextState.GetDeactivateOnReturn(@lGetDone)
* Handle transaction setting (Consistency)
* Values: 0 - commit, 1 - abort
lTxnState = 1
oContextState.SetMyTransactionVote(lTxnState)
oContextState.GetMyTransactionVote(@lGetTxnState)
These samples show how to use the new COM+ Events (also known as Loosely Coupled Events or LCE) model with VFP 7.0 servers. With COM+ Events, you have two options for creating events.
Persistent Subscriptions. With Persistent Subscriptions, you have a publish/subscribe relationship where multiple clients can subscribe to a single published Event. The key word here with Persistent Subscriptions is "class". Objects do not need to exist for events to occur since they are handled by the COM+ Event System managed by the operating system. Subscriber objects are created (and subsequently destroyed) only when an event is triggered. A single event can trigger multiple Subscriptions. Additionally, you can set up filtering conditions so that a Subscriber event is only triggered when a particular condition is met. Persistent Subscriptions are stored in the COM+ catalog and survive system shutdowns.
Transient Susbscriptions. Rather than being tied to classes, Transient Subscriptions work with existing objects. They are also stored in the COM+ catalog, but they do not survive a system shutdown. In addition, you cannot have multiple Subscriber objects receiving the event. Transient Subscriptions offer the advantage of having objects always being live, so performance is maximized.
The COM+ Component Services MMC snap-in allows you to manage Persistent Subscriptions through a nice user-interface. With Transient Subscriptions, you need to set up events using administrative API routines. The Transient Subscriptions example below shows how to do this.
The samples below show a simple Book Company that needs to perform two actions:
Both of these actions are considered events in the COM+ Events system. An Event is set up in the system merely to establish the specific methods (and parameters) for Subscribers to use. The Event is simply a VFP COM Server with a class definition containing no code (i.e., Interface). Subscribers need to be able to IMPLEMENT that interface so that specific code can be written to handle events. With VFP 7.0, your COM Servers can IMPLEMENT the Event interface.
The Persistent Subscription sample shows setup and creation of a common COM+ Event using VFP7 Servers. Much of the work involves using the Component Services MMC (Microsoft Management Console) similarly to how you may have worked with Microsoft Transaction Server packages.
· Click on Install new event class(es) button,
· Select the FOXBOOK_PUB.DLL file,
· Click Next buttons until done
· On the second page, select the check box at bottom labeled ‘Use all interfaces for this component’ and click the Next button,
· Click on the FOXBOOK_PUB.BOOKPUB ProgID that appears in the list box,
· Click Next and enter a name for subscription (e.g., Fox Subscription 1),
· Click on Enable this subscription immediately checkbox and Next/Finish buttons until done
You are now ready to try out your Persistent Subscription. Run the BOOKS.SCX form (in FOXBOOK_CLIENT project) which uses a sample BOOKS.DBF table. You can click on the New Book button to bring up a dialog for adding a new book. When you do this and click OK, a record is inserted into the BOOKS table and the NewPrice method on the Event is called. This causes an event to be triggered for all Subscribers. In our example, the NewBook event code in the Subscriber writes out an entry to the audit log. You can click on the Audit Log button to see this. In addition, you can see the active objects in the Component Services MMC window. The PriceChange event works the same way. Take a look at the code in these forms to see how event gets triggered.
COM+ Events lets you control a number of factors related to events that may be of interest in your application. At the Publisher end, you can open up the Properties dialog in Component Services window. The Advanced tab has several options under the LCE frame including ability to allow in-process Subscribers. Perhaps most interesting is ability to add Parameter Filters for Subscribers. If you open a Subscriber's properties dialog, the Options tab has a text box called Filter Criteria. You can enter a valid condition string here to control whether an event fires for a Subscriber. For example, you may have a Filter set for a specific book (e.g., cBookName="Biking Across the Road"). A Subscriber object is only created if criteria for the filter are met.
As mentioned earlier, Transient Subscriptions cannot be created using the COM+ Component Services Explorer. Setup is handled via the COM+ admin objects. The Publisher and Subscriber are created only when needed. Although Transient Subscriptions are stored in the COM+ catalog, they do not survive a system shutdown. In fact, since this information is not persisted, your Subscribers do not need to be VFP COM servers.
Before you begin, make sure you update any necessary constants in EVENTS.H. Also, the FOXBOOKS_PUB.DLL file needs to be created (this was done automatically with Persistent Subscriptions sample above). Because there is no setup required for Transient Subscriptions, you can run the sample now. Launch the TCE_EVENTS.SCX form (in FOXBOOK_CLIENT project). There are several sections on this form. Click the Create button to create a COM+ Event . At this point, we have created a new PROGID which you can test in the Command Window (this is one stored in EVENTS.H file -- "Books.Publisher"). Type oPub = CreateObject("Books.Publisher") and then try using Intellisense in the Command Window typing oPub. . At this point, we have only created an Event, so no action occurs if you enter something like: oPub.PriceChange("Foo",100).
Next click on the Subscribe button. We now have a Subscriber set up to handle the Event. This Subscriber object is an instance of the class definition in the BOOK_TCE.PRG file. Notice that this class IMPLEMENTS the same bookpub interface used in the Persistent Subscription example. And the action of the user events is to write out an entry to the audit log file.
We can now test our Transient Subscription sample by running the BOOKS client form again and clicking on the Change Price... button. Make sure to select the TCE checkbox. If you view the audit file, you will see a new TCE entry.
Queued Components (QC), a key feature of COM+ and based on Message Queuing Services (MSMQ), provides an easy way to invoke and execute components asynchronously. Processing can occur without regard to the availability or accessibility of either the sender or receiver. A shop-at-home television network is an example of asynchronous processing. Viewers call in their purchases and the information is taken by order processors — who may or may not be connected to the server. In an asynchronous, disconnected scenario, the orders are taken as quickly as they can be phoned in and are queued for later retrieval and processing by the server.
The sample below is a simple Pizza Order system in which customer name and pizza order is made to a VFP COM server running as a Queued Component. The calls are made and the order is sent to a simple Order text file. Because they are Queued, the orders are made asynchronously, so many can be taken at once. Depending on how the QC application is setup, you can have the orders simply recorded (where no Order text file is created) and played back later.
Note: for purposes of the sample only, we are not enforcing any security settings. Production applications should always enforce strong security.
Note: if you are not running from local machine, set the QC_MACHINE #DEFINE of the FOXQC.H file to your specific machine name where COM+ Application is located and recompile the Pizza form.
· Quit your running instance of VFP
· In Component Services explorer, Shutdown the QC1 application if it is running.
· In Properties dialog of QC1 application, uncheck the 2nd Listen checkbox in the Queuing tab.
· Restart VFP and run the Pizza form. Click on the Cleanup button to delete any existing order text file.
· Select ingredients you want for a new order and click on the Place Order button. You will not see an Order file open. You can click Refresh all you want, but it isn't there since the Queued Components listener is turned off.
· Quit VFP again
· In Component Services explorer, Shutdown the QC1 application if it is running.
· In Properties dialog of QC1 application, check the 2nd Listen checkbox in the Queuing tab.
· Restart VFP and run the Pizza form. Notice the View button is still disabled since Order text file still doesn't exist.
· Back in the Component Services explorer, Start the QC1 application.
· In VFP, click on the Refresh button of the Pizza form. You should now see View button enabled. Click on it to see your earlier recorded order now appear.
This example is quite simple. Take a look at the Pizza class in FOXQC.PRG. Classes registered for Queued Components have specific requirements. These include write-only properties (see the _COMATTRIB settings), no return values for methods (see AS VOID settings) and no in/out parameters (by reference). You should also avoid passing objects as parameters or setting properties to object references. Because the server object is not available when the client makes the call, server results can be returned by sending a message that creates another object. In this way, two-way communication occurs not in every case but only when it is required, by a series of one-way messages between objects. Since actual actions are recorded in a queue, you should take precautions to ensure your code does not rely on either a specific machine (since it could be run back on a different one). Also, the code may be played back hours or even days after the original recording was made.
The MSMQ Workgroup configuration does not permit Queued Components to support application security. If you've installed MSMQ with the "Workgroup" configuration, you must also disable security on each Queued Application accessed in this configuration. In the Properties dialog of the COM+ Application with your Queued Component, select the Security tab and change Authentication Level for Calls to None. In this sample, we are not using transactions. However, because MSMQ supports transactions, its probably a good idea to setup your QC application using transactions (see the Transaction sample for getting an ObjectContext reference and calling SetAbort/SetComplete).
Open up the TESTQC.PRG file to see how clients need to make calls to a QC component. There are two important steps. First, you need to make sure the COM+ application containing the Queued Component is running. You can automate starting this using the COM Catalog Administration API routines such as in the example. The second step is to create an instance of the component using the special Queue moniker and GetObject(). Instead of CreateObject() or NewObject(), you need to go through the Queued moniker so that COM calls are recorded instead of being made immediately.
One of the exciting new COM+ Services for VFP developers is that of Compensating Resource Managers (CRMs). A CRM provide a quick and easy way to integrate application resources with Microsoft Distributed Transaction Coordinator (MS DTC) transactions. It is an alternative to developing a full Microsoft Transaction Services resource manager. CRMs allow you to have user code participate in transactions. You can now write VFP COM servers that get triggered when a transaction is committed or aborted.
The sample below shows CRMs in transactions that create or delete files. One of the components of your distributed application might want to create a new file. With a CRM, you can have that file removed if the transaction is aborted. The good news for VFP developers is that with CRMs you can now have VFP data participate in transactions. For example, you can use a CRM to rollback records added to a VFP table if an abort occurs.
· CrmFilesClient.CrmFilesVFP - Required
· CrmFilesVFP.CrmFilesCompensatorVFP – Not Supported
· CrmFilesVFP.CrmFilesWorkerVFP – Required
A CRM is provided as a pair of COM components – a CRM Worker and a CRM Compensator. The CRM Worker is responsible for doing the main work of the specific CRM. The CRM infrastructure provides an interface to the CRM Worker through which the CRM Worker can write records to a durable log file on disk. The CRM Worker must write records to the log and make them durable before it performs its work so that, if a crash occurs, recovery can occur correctly.
The CRM Compensator is the component that is created by the CRM infrastructure at the completion of the transaction. It implements a defined interface by which the CRM infrastructure can pass on notifications of transaction completion and the log records that were previously written by the CRM Worker. Because VFP COM servers have a single-threaded apartment model, it is important that you set the synchronization property to “not supported” in order to avoid a deadlock in the prepare phase.
CRMs are intended for advanced developers since you will need to handle a variety of possible scenarios that can occur with your application including that of a crash which could occur between the time the log is written out and the actual action is taken.
The CRMWORKER.PRG file contains the OLEPUBLIC class used by the CRM Worker to handle writing out to log and actual action (create or delete file). Notice the CREATEOBJECTEX() call. This is used to create an instance of “CrmClerk.CrmClerk.1” which does not support an IDispatch interface. We can then write to the durable log with this object. The WriteLogRecordVariants() function, which writes out the durable log, must be passed an array which is zero-based (hence use of COMARRAY function). The CRMCOMP.PRG file contains the CRM Compensator class. Notice that it has an IMPLEMENTs clause so that it can implement the interface for the compensator events. These events allow you to handle commit or abort actions against the transaction. The pLogRecord variant parameter returned by several events contains the array written out to the log by the CRM worker. The CRM flags and sequence number are appended as the last two elements in the array (see the ICrmCompensator::PrepareRecord topic in MSDN for more details).