Jim's Pages => Java Pages => Messaging Pattern
This page discusses a number of patterns used in message-based systems. These are not all industry standard terms. It was written to give [proprietary middleware] users a common vocabulary, even if some of the words are made up on the spot. It gives only very brief descriptions, and does not use the formal pattern definition format used in many books and publications. If [the middleware] is widely adopted, we might consider expanding it into that format to give a better feel for how and when these can be used.
Some messaging discussions, like the new SOAP standards (Part 1, Part 2) distinguish Document messages that simply send data from one system to another from RPC messages that follow a standard for encoding remote procedure calls and their results. This page does not worry much about the content of the message; most patterns could be applied to either type of message.
For more rigorous coverage from some very smart folks, see the Enterprise Integration Patterns site.
This is a one-way message. No response is expected. This is often used for logging. It might also be used for business updates if the requester trusts the message service and the transaction itself to not fail.
Sender Receiver
------------ ------------
Message
+------------>
A requester sends a message and expects a return message. This is a variation of remote procedure call. I've used asynchronous RPC many times to retrieve data while keeping the keyboard and mouse free to navigate. That's not always easy in languages that don't support threading!
Client Server
------------ ------------
Request
+------------>
Response
<------------+
One party sends a message to everybody on the message bus. Some varieties use IP services to broadcast to everyone on the network. (!) Others use the Publish-Subscribe model below, perhaps with automatic subscription for all registered nodes. The message is usually a Datagram as the broadcaster does not expect a response from each receiver.
Observer
In the listener flavor of publish subscribe, listeners add themselves to a publisher which originates messages. The messages are often event notifications. Java Swing and other MVC user interfaces use listeners heavily and you can use the same Observer and Observable classes.
Subscriber1 Subscriber2 Publisher
------------ ------------ ------------
Subscribe
+------------------------->
Subscribe
+------------>
* Some event of interest happens
Message
<-------------------------+
Message
<------------+
Publish-Subscribe
In a variation, subscribers sign up with an intermediary to receive messages. The intermediary manages all subscribe and unsubscribe operations, and perhaps maps message types to subscribers. Message originators pass new messages to the intermediary, which then passes them to subscribers. This has some interesting features.
Subscriber-1 Subscriber-2 Intermediary Publisher-1 Publisher-2
------------ ------------ ------------ ------------ ------------
Subscribe
+------------------------->
Subscribe
+------------>
* Some event...
Message
<------------+
Message
<-------------------------+
Message
<------------+
* Some event...
Message
<-------------------------+
Message
<-------------------------+
Message
<------------+
Messages are usually datagrams as the publisher does not expect responses from the subscribers. But, per George Gershwin, it ain't necessarily so. A listener might return a boolean indicating it has "consumed" the event and the publisher should not pass the event to any more listeners. Another variation is polling or voting. A window manager might close a frame only if all sub-windows report that they are in a suitable state.
Holding messages for unavailable subscribers has its challenges. It usually uses a queue per subscriber. The publisher might put a limit on message age or queue depth to avoid holding an excessive volume of messages for subscribers who might never pick them up.
Message systems such as MQ-Series and JMS are usually asynchronous. After the sender puts a message on a queue the sender is free to continue with other activities. In a request-response pattern the sender receives a response some time later. This adds some interesting challenges:
To help match responses and requests, most messaging products generate a correlation id when sending the request, and put the same id on the response message. Simple systems may handle to the "response arrived" event in a generic way without matching the request. In more complex systems it may be useful to store a map of command objects keyed by the correlation id, and fire the command that matches the id on the response.
I used this technique with a controller that processed a sequence of steps. One step in the sequence required data from an asynchronous source. If the data was already in memory, the controller walked right through the steps. If the data was not present, the controller sent the request and cached a command that resumed the sequence. When the response arrived the controller retrieve the command from the cache by correlation id. When executed, the command told the controller to resume the sequence. If the user grew tired of waiting and cancelled the operation the controller pulled the command from the map so the response event just threw the response away.
Client Controller Command Cache DataSource
------------ ------------ ------------ ------------ ------------
RunSequenceOne
+------------>
DoStep-1
<-+
DoStep-2
<-+
GetData
+-------------------------------------->
Create
+------------>
PutCommand
+------------------------->
* Time passes ...
HeresYourData
<--------------------------------------+
GetCommand
+------------------------->
Execute
+------------>
ResumeSequenceOne(3)
<------------+
DoStep-3
<-+
Even with an asynchronous messaging foundation, one can make the request-response cycle synchronous. Some component in the call stack sends the request and blocks while waiting for the response. Any client that calls this component sees the request as blocking and synchronous and gets the response back like a normal remote procedure call. MQ-Series provides an API called "Get and Wait" (which waits and gets) to block the client unit the response is available or a timeout expires. If the messaging product does not provide such an API, one can write a component to do the same job.
Be aware that using messaging as an RPC is usually not a usually high-performance option. However, in practice we have executed request and response messaging from Windows desktops to distant CICS mainframes over MQ-Series in under a quarter second. That kind of performance is not typical, and a sub-second requirement is not usually an indicator for messaging. Use designs like aggregate responses (discussed below) to minimize the number of trips through the messaging middleware.
The following are shown as variations on Request-Response. As far as the client is concerned, the interaction is still:
Client Server
------------ ------------
Request
+------------>
Response
<------------+
Usually a given request type will result in a response of a known type but it may be useful to have a single request type map to several response types. For example, a "get product information" request with a generic product number argument might get a "blender" response or a "toaster" response. The requester may or may not be able to predict the response type. The client might examine a type field and build one of several objects from the response data.
A response may contain one or many multiply occurring or optionally occurring structures. For example, a request for all products for a customer may result in zero or more toasters, blenders, dishwashers or other types. Again, the requester may or may not be able to predict the response types.
As an alternative to an Aggregate Response, one request might generate multiple responses. This implies asynchronous communication for all responses or all but the first response. It has all the challenges of the Asynchronous pattern and unpredictable responses, plus the requester might not know when all possible responses have been received. I have used this pattern with a client that could accept only a single simple object in the response, no concatenated or nested data structures or complex object graphs.
Client Server
------------ ------------
Request
+------------>
Response-1
<------------+
Response-2
<------------+
These variations may have you thinking they'd work so naturally in XML that you wouldn't even think of them as unusual patterns. Indeed XML's ability to express concatenated and nested structures is a wonderful thing. These patterns arose in a system using one COBOL copybook per response on the mainframe as the golden description of data structures. Concatenation and nesting, while certainly possible in COBOL, were not supported that time around.
Any request may trigger an arbitrarily complex process at the other end. Variations such as Multiple Choice, Aggregate and Multiple Response seem to imply processes that can be broken down by functional decomposition. A general purpose or special purpose Message Broker may be involved in these. The visual pattern looks the same for all four of the following.
Client Broker Service-1 Service-2 Service-3
------------ ------------ ------------ ------------ ------------
Request
+------------>
Request
+------------>
Request
+------------------------->
Request
+-------------------------------------->
Response
<------------+
The broker inspects the request and, based on data within the request, chooses one of several services. (Mentally add an exclusive or across the three services drawn above.) The protocol from Broker to the end services may be the same messaging service any other program-to-program communication, including direct calls native to the implementation language. This maps nicely to the Multiple Choice pattern.
The broker executes several services one after another. It might generate a single response, an Aggregate Response, or Multiple Responses. I built one broker that took an abstract name for a set of requests and built concatenated results. It was a convenient way to batch up requests for fewer trips across the network. We did this only after testing proved that fewer large messages were more efficient than more small messages, which may not always be true.
Adds logic to sequential. The broker invokes the first service. It then inspects the response data or status, and chooses the next service. It may repeat for any number of services and generate a single response, an Aggregate Response, or Multiple Responses.
The broker manages a unit of work across several services. This is similar to Workflow with multiple services that perform updates. It hints at a complex request, likely an Aggregate message in its own right.
With messaging, it is usually not possible to do the kind of "all or nothing" transaction or "unit of work" logic that we find in databases. When a client sends a message a "success" return code from the middleware means only that the message was put on a queue. It does not mean the message actually arrived at the end service, or that the end service was able to process it, or that a response will ever be sent. With asynchronous messaging, the ultimate success or failure of the action may not be known for some time. If it is necessary to support roll-back, one might be forced to build a broker that knows how to send "compensating transactions" to undo updates that may have been applied or may still be waiting in a queue.
In short, distributed transactions are very hard! Try to avoid them.
About those text diagrams ... In the mid-90s I started seeing message trace diagrams in books and magazines. I made a few like these with a text editor, but it was very painful to get the alignment right. So I made a macro for my text editor that reads a little description file and generates these lines. Good clean fun. In 1996 we started using Objectory and then Rational Rose, but I still keep my little diagram tool around for quickies like these. Here it is as standalone REXX: oochart.rex
| Revisions | |
| 03/10/2003 | New |
| 06/20/2003 | Added link to oochart.rex |