This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
|
Putting Microsoft Message Queue to Work
Chris Dellinger |
MSMQ is a cool new product, but where should you use it? We'll take you through some real-world projects that really take off with MSMQ support. |
All right, you know what MSMQ is, but perhaps you are having a hard time finding practical applications for it in your enterprise development. (If you don't know what MSMQ is, see "Microsoft® Message Queue is A Fast, Efficient Choice for Your Distributed Application," by David Chappell, in the July 1998 issue of Microsoft Systems Journal.) Or maybe you're thinking, "It sounds great, but can't I already do the same thing with existing tools?" To answer these and other questions, let's take a look at two real-world applications I have been working on that show where MSMQ really excels.
Any new technology must offer a compelling reason to use it. For me, the technology should meet one of four criteria: previously unavailable functionality, better performance, less code to write, or downright coolness. MSMQ is one of those rare technologies that meets all four. Among my favorite features of MSMQ is its offline processing support, commonly called store and forward. With store and forward, an application writes information to disk when it's disconnected from the network, and later, when network connectivity is restored, routes this information to the appropriate location. If you've done any serious enterprise development, you've had to write this type of code at one time or another, especially when dealing with communication between two systems. Thankfully, those days can now be put behind you. With the introduction of MSMQ, developers now have a far more robust and better-performing store-and-forward mechanism than they could reasonably write on their own. And MSMQ is supposed to be the messaging standard for Windows NT for years to come (no more reinvention of the wheel). Best of all, as you'll soon see, MSMQ provides these store-and-forward features with minimal coding. Already, MSMQ has met my requirements of previously unavailable functionality, code reduction, and coolness. Another great feature of MSMQ (one that helps meet my criteria of improved performance) is its ability to send messages asynchronously. There are quite a few areas in today's distributed systems where performance can be improved through asynchronous messaging.
Store and Forward
A Real-World Example
|
I was confronted with the dilemma of determining the best way to package this logic. One possibility was to have all components invoke MSMQ directly and send the messages to the error-handling queue. Another solution was to create an ActiveX-based server that contained the error-handling logic, which could then be invoked by the application experiencing the error. Even though the MSMQ ActiveX wrapper can be easily invoked from anywhere I am currently writing code (in Visual Basic®, stored procedures, ASP scripts, and so on), I decided to write my own ActiveX error-handling object. In the long run, I believe it reduced the amount of code all the developers on my team had to write and ensured one consistent way of communicating with the error queue. Once implemented, this ActiveX server would be deployed to run locally on each of the machines that could possibly access it. The properties and methods for this ActiveX server are outlined in Figure 3.
In addition to sending error messages to the queue, the error object was also designed to receive messages from administrators. During development and testing, it is quite common to log informational messages concerning the state of an object in addition to error messages. This gives developers of distributed systems an easy way to monitor the flow of the system and catch programming errors. Once the system goes live, administrators usually do not want to be inundated with non-error messages. For this reason, the error object was designed to read the proper level of message logging from an actual MSMQ queue.
Whenever the error object is instantiated, it attempts to read the current logging level from a public queue. If the error object is unable to access the queue, it assumes that it is working offline and uses the logging level that was last read from the queue. Developers who utilize the error object are forced to specify the error level when they log the messages. The error object filters out the messages based on the messaging level specified by the public queue. In situations where the system is up and running and an unexpected error keeps occurring, administrators can simply send a message to all the components within the system to increase the level of messaging to help them resolve the problem. The code for instantiating the object can be found in Figure 4, and the code for logging error messages can be seen in Figure 5.
Asynchronous Communication
Another great reason to use MSMQ is to increase the speed of your applications with asynchronous communication. A message sent through MSMQ is always sent asynchronously; in contrast, calling a method of an ActiveX server is asynchronous operation.
In many situations, you can get better performance by offloading resource-intensive business logic from the client onto more powerful machines. This n-tier, or distributed, approach can also improve performance in circumstances where the client machine is in a remote location and numerous database calls are required by the business logic. In this situation, it is much more efficient to have the business logic executing locally to the database than it is to have numerous database calls executing remotely across a WAN.
Unfortunately, even the best-designed ActiveX-based
servers can at times take large amounts of time to finish processing, which in turn can paralyze client applications.
Reasons for this can range from the execution of very complex business logic to the process of communicating with
several systems across a slow network. This delay can be extremely frustrating for users who want to kick a process off without locking up their computer waiting for the ActiveX server to finish.
Until the advent of MSMQ, there were very few solutions to this problem. One of the easiest ways to improve system performance is to buy faster machines and network connections; however, this approach still has its limits. Other solutions include using threads in the client application, coding callback routines into creating a rudimentary messaging application that writes information to disk for later access and processing by a server running in the background. Again, all of these solutions are quite code-intensive.
By using MSMQ's asynchronous capabilities, developers are able to create fast solutions with very little coding for situations where utmost performance is demanded. As stated earlier, ActiveX servers do not return control to the calling application until the server finishes processing, tying up the client application. With MSMQ, the client application will send all the information that normally goes to the server (through properties and parameters) to a queue instead. As soon as this information is successfully sent to the queue, control is returned to the client application, freeing it to proceed with other tasks.
Meanwhile, a server process running in the background periodically reads the message queue to see if any new messages have been sent. Once a new message has been received, another ActiveX server is called to help process the information. This is the same server that would have been called directly by the client application prior to the introduction
of MSMQ. This server processes in the background, minimizing its effects on the client application's performance.
In situations where the client application needs to know the
outcome of the server's processing, the server can write the outcome information to another message queue. The client application can then periodically, or upon demand, check this queue for a return message. This solution is similar
to a callback server, except that far less coding is required and a consistent method can be employed for multiple
ActiveX servers.
This type of solution works equally well in both online and offline situations as long as the client application follows the store-and-forward rules outlined previously. For example, I am currently working on an application for a manufacturing company. The application collects new order information, performs some proprietary business logic (credit checks, cost calculation, and so on), and then writes the data to a series of database tables.
There are several flavors of client applications for this system: a laptop client for users who are located in the field disconnected from the corporate network, a Windows 95-based version for users who are processing sales orders over the phone while connected to the corporate network, and finally an Internet browser application that allows users from all over the world to order the company's products online. In all three cases, basic order information is collected and must be saved to the database. The customer will be contacted directly by a customer service representative later if for any reason the order application is turned down or additional information
is required.
The same business logic is required to process the information for all three clients. In each implementation, this information needs to be collected as quickly as possible to avoid tying up the client computer. This is especially important in the online and phone user situations, where delaying the customer for a prolonged period may result in lost business. For these two reasons, it was an easy decision to break the similar business logic into an ActiveX server, which I will refer to as the Orders server.
MSMQ is needed to handle the needs of the system's laptop users, who are never directly connected to the corporate network while on-site with a customer. Before MSMQ, I'd have had to write some mechanism to take data entered by the user and cache it to disk. I'd then have to write another application that would be instantiated the next time the laptop computer was attached to the corporate network. This second application would be responsible for reading the data that had previously been cached to disk and sending it on to the Orders server. Needless to say, this type of solution would be quite code-intensive, with extensive amounts of time being spent in development and testing.
With MSMQ, I was able to cache the data to disk and have it automatically routed to the Orders server the next time the laptop was connected to the corporate WAN, usually through a RAS dial-up connection. The code for this type of solution was very easy to write, being very similar to the code outlined previously for the error handler.
Figure 6: Original Order Processing System |
Once the data is successfully delivered to the NewOrders message queue on an MSMQ server, another ActiveX server, FindOrders, processes each order, or message, by instantiating the Orders server and passing it all the necessary information (see Figure 6). The FindOrders server is constantly running, processing new messages from the queue as soon as they appear.
With this solution, all three client applications could now access the Orders server easily, and the offline users were able to store new order information locally, routing it for processing the next time the computer was online. However, after some prototyping it was determined that the system was still running too slowly for the two online scenarios. During peak business hours, the business logic that was occurring in Orders and its underlying servers was taking up to three minutes to process. Upon further investigation, several performance bottlenecks were discovered within the Orders server. During processing, it interacts with several systems (some mainframe, some Windows NT-based) and databases. Even with hardware upgrades and performance tuning, the Orders server would probably never be able to process a new order in under a minute, which was totally unacceptable from the viewpoint of the client. In comparison, the laptop portion of the system was operating quite efficiently, owing to the fact that sending information to an MSMQ queue was almost instantaneous. For speed reasons alone, it was an easy decision to send all new orders, regardless of origin, to the NewOrders queue instead of directly accessing the Orders server. Multiple FindOrders and Orders servers were instantiated on several machines to alleviate the additional load caused by routing all orders through the NewOrders queue. My customer was happy because all the orders were being processed quickly, usually within an hour or two, and the customer was only being detained for the time needed to collect all the vital information. This new configuration can be seen in Figure 7. |
Figure 7: Streamlined Order Processing System |
Packaging Information
|
|
Later on, this recordset can be recreated in memory through the following line of code: |
|
Whenever a new order is placed, it is added as a new record in the recordset. Depending how the system is configured, more than one order can be added to a recordset before sending it as
a message to the queue. The code for adding a new order to the recordset is shown in Figure 8; the code for sending the recordset to the appropriate queue can be seen in Figure 9.
It's worth taking a look at the logic for the FindOrders ActiveX server. FindOrders processes all client orders by first reading the ADO recordset from the New Orders queue. It then passes this recordset as a parameter to the Orders server. The Orders server performs some fairly complex business logic (this is the code that can take several minutes to run). After the business logic finishes processing, the new order information is inserted into a SQL Server database table.
Transactions
|
|
Basically the developer must instantiate new MSMQTransactionDispenser and MSMQTransaction objects.
Next, the BeginTransaction method of the MSMQTransactionDispenser object must be called. This method returns a new MSMQTransaction object, which can be used whenever any messages are sent or retrieved. Finally, the transaction can either be committed or aborted by calling the Commit and Abort methods of the MSMQTransaction- object.
In transactions with more than one resource manager, developers will normally use external transactions or MTS transactions, both of which use DTC. DTC external transactions are coded very much like internal transactions: |
|
The major difference is that external transactions are necessary when a transaction includes actions beyond simply sending or retrieving MSMQ messages. In these situations, the application must ask DTC for a transaction object and explicitly reference that object each time it sends a message, retrieves a message, or executes an action upon another resource manager. The only difference in the code is that the MSMQ Transaction Dispenser object is replaced with the MSMQ Coordinated Transaction Dispenser object.
Of all the transactional processing methods I've used, MTS is my favorite because, in my opinion, it is the easiest to code. I like to follow a distributed approach, with most of my business logic residing in ActiveX servers. By running within the MTS environment, MSMQ uses the current MTS transaction if one is available. In this situation, MTS uses the services of DTC for transaction coordination. The developer writes the MTS server code in the exact same way they would when dealing with database transactions. The code for the FindOrders server can be seen in Figure 10.
Conclusion
|
From the August 1998 issue of Microsoft Interactive Developer.