The technologies we have discussed so far are primarily aimed at time-dependent applications. In a time-dependent application, the caller and callee must coexist at the time a method is called. If the callee is unavailable for any reason, the method call will fail. To recover, the caller must retry the method call at a later time.
Many distributed applications can be partitioned into time-independent sections. For these applications, it's more important that a method call be processed eventually—without coding retries into the caller—than that it be processed immediately. Some classic examples of applications that have time-independent characteristics are listed in the following table.
|Banking||Money transfer, foreign exchange|
|Manufacturing||Factory floor automation|
|Railway||Freight management and control|
|Retail||Ordering, fulfillment, shipping, and billing|
For instance, consider the case of a company that takes customer orders over the phone. When a customer calls in an order, the person taking the order needs to give the customer an order confirmation number while the customer is still on the phone. This is time-dependent behavior. However, there is no need to keep the customer on the phone until a confirmed ship date is received from the shipping department. All that's needed is to assure the customer that the order will be scheduled for shipment. This is time-independent behavior.
Message queuing middleware can be used to implement time-independent applications. Communication occurs using one-way messages, which are sent to a queue by the caller for later retrieval by the receiving application, as shown in Figure 6-4. The calling application opens a queue and issues a request by sending a message. The message queuing software does the work required to route the message to its destination. An application at the other end watches the destination queue for incoming messages; when it receives a message, it reads the message, figures out what to do, and does some work. The contents of a message are meaningful only to the sender and the receiver. If the receiver needs to communicate back to the sender, it either sends a message back or updates some common data store that the sender can query. Message queuing is similar to e-mail, except that it is designed for application-to-application communication rather than person-to-person communication.
Figure 6-4. Connecting time-independent applications by using message queuing.
Message queuing offers some attractive benefits to application developers. Message queuing improves server availability by ensuring that eventually all calls will get through to the server. This capability is particularly useful for disconnected clients or for clients connected via unreliable links. In addition, if a server is temporarily busy, calls are simply placed in a queue instead of timing out. This helps servers deal with peak-time or increasing demands more gracefully. Message queuing improves reliability by ensuring that messages are delivered exactly once, even if the application, communications link, or system fails. Message queuing also provides scheduling convenience. Queues can be configured for optimal throughput. Most queuing software even lets you prioritize messages to ensure that the most important messages are delivered first. Server lifetime is completely independent of the calling application since servers retrieve messages at their convenience.
MSMQ, Microsoft's message queuing product, provides the infrastructure for managing queues and routing messages between queues located on different machines. MSMQ also provides an API for creating messaging applications and administrative tools for configuring and monitoring queues. Although messaging is not based on COM, MSMQ provides a set of COM components to help developers write messaging applications. We will see how these components are used in Chapter 15, when we discuss adding messaging support to three-tier distributed applications.
MSMQ provides an OLE Transactions resource manager, so queues can be treated as transactional resources. If a queue is configured to be transactional, the act of sending a message to the queue or receiving a message from the queue can be transacted. Thus, message queuing operations can be composed with operations on MTS objects using MTS automatic transactions. For instance, a business object in our order entry example might take the following actions within the ConfirmOrder method:
If the business object can't get an order confirmation number because the order isn't saved properly, the ConfirmOrder operation should fail and no message should be sent to the shipping department. The ConfirmOrder operation should also fail if the message can't be queued for the shipping department. As we saw in Chapter 4, automatic transactions provide a straightforward way to implement this kind of behavior. Failure of either operation causes the transaction to abort, and an error is returned to the client application.
It's important to note that sending messages and receiving messages are parts of two separate transactions, as illustrated in Figure 6-5. Because the sender and receiver aren't necessarily connected and there can be a substantial time delay between sending and receiving a message, a single transaction is not appropriate. Transaction success on the sending side does not imply that the receiver will be able to successfully handle the request in the message. For instance, in our order entry example, the order entry application can accept an order regardless of whether all the ordered items are in stock. When the shipping application receives the shipment request, it won't be able to send the out-of-stock items. In situations like this, applications must provide a compensating transaction. A compensating transaction doesn't undo a previously committed transaction, but it can undo the effect of the transaction. For example, the order entry application might compensate for shipments held up by out-of-stock items by sending the customer a postcard notifying her of the delay and giving her an opportunity to change the order.
Figure 6-5. Transactions in a message queuing application.
Message queuing is a useful interoperability mechanism in addition to providing a way to write time-independent applications. To communicate with another application, you simply send a message to a queue with any information the application needs. The message queue server doesn't care what the contents of the message are—it just passes the message along. If the application already supports message queuing, it will receive the message from the queue and take the appropriate action. If the application does not support messaging, a receiver application can be written that receives messages from the queue and translates them into native calls to whatever API the application exposes. This mechanism can even work for applications that run on different platforms, so long as the message queue servers on the two platforms agree on the general format of a message and on how to move messages from one queue to another. When the message queue servers do not agree, a bridge can be used to translate from one message format to another and to move messages between queues.
Although MSMQ is available only on Windows platforms, the FalconMQ product from Level-8 Systems provides a client API for UNIX, AS/400, VMS, CICS/MVS, and other platforms that lets developers work with MSMQ queues. FalconMQ also contains a bridge providing interoperability with the very popular IBM MQSeries message queue server.