Two MSMQ Scenarios
R.L. Parker
In this article, R.L. discusses
two scenarios that have presented difficult problems for VB programmers
in the past. Both scenarios involve physically separate applications that
must collaborate to get some work done, even in the absence of a persistent
and reliable physical connection. R.L. shows how Microsoft Message Queue
(MSMQ) provides the infrastructure that makes solving these kinds of problems
possible and goes on to suggest a higher-level abstraction of the problem
that unifies the two scenarios -- an abstraction he calls "asynchronous
remote method invocation."
Suppose we have two or more separate computer applications that need to
collaborate to get a job done. Furthermore, suppose that the physical connection
between the two systems is intermittent or unreliable. Since real-time communication
between the systems isn't feasible, we need an alternative communication
infrastructure. This alternative must detect when a physical connection
becomes available and guarantee data transmission between the two systems
despite any problems that occur in the physical or lower-level software
layers. If it's not possible to complete the transmission, our infrastructure
needs to re-try the transmission the next time the physical connection becomes
available. If this sounds hard, you're right. It is. And it's been done
before -- lots of times. People have been writing message queuing engines
ever since mainframes. Fortunately for us, Microsoft provides a ready-to-use
engine that works under Windows NT server. Its name is Microsoft Message
Queue (MSMQ).
The "lob" scenario
There are a couple of variations on the
problem. In the first variation, an application is divided into three parts.
The first part does sensing, sampling, or some other data collection function.
I'll refer to the first part as the "field" part. The second part
of the application processes the data that was collected by the field part.
I'll refer to the second part as the "headquarters" part. Finally,
the third part is the infrastructure part responsible for transporting the
data from the field part to the headquarters part.
I call this the "lob" scenario, because the field part just "lobs"
the data over to the headquarters part. The field part doesn't care what
happens to the data after it lobs it. It has no further responsibility in
terms of data or application integrity. We'll see later how this contrasts
with the second scenario.
Let's rephrase the lob scenario in n-tier terms: The field part of the application
wants to invoke a method on a headquarters business object. Let's refer
to this as a "remote method invocation." The logical design for
such a solution is shown in Figure
1.
If we have a reliable, high-speed connection, we could set up the physical
design identically to the logical design: We could develop a business object
that the field application could invoke remotely via, say, DCOM. That is,
the third part of our application would be implemented by synchronous remote
method invocations on the headquarters business object.
However, if we only have an intermittent connection, this approach won't
work. MSMQ (see the sidebar, "Quick Intro
to MSMQ") provides the infrastructure
that solves our problem. We can use MSMQ to implement the third part of
our application. This refinement is shown in Figure
2.
Notice that we've added a business object that runs locally for the field
application to use. The local business object exports the same interface
as the "real" business object that runs at headquarters. Using
this technique, we can insulate the application from the fact that sometimes
the remote object is available and sometimes it isn't. Another way to characterize
this solution is by saying that the local business object acts as a "proxy"
for the "real" business object. Our field application can always
invoke methods on this "proxy" object, without regard to connectivity
to the outside world, as depicted in Figure
3.
The following numbered steps match the numbers in Figure
3:
1. The application calls a method on the local business object (let's name
the method "AddItem"). In the lob scenario, the local business
object's "AddItem" method is implemented by sending a message
(via MSMQ) that will ultimately result in a call to the "AddItem"
method on the headquarters business object. Note that the field system is
set up as an MSMQ "independent client." As we'll see, this is
transparent to the local business object (the MSMQ client). It enables the
store-and-forward functionality that the local business object will rely
upon to transport its method invocations across to the real business object
at headquarters.
2. On the headquarters side, we need a "monitor" object. Its job
is to read the messages as they're delivered to the queue. As we'll see,
the monitor can be event-driven.
3. When the monitor detects that a message has arrived in the queue, it
decodes the message and then calls the appropriate method on the headquarters
business object.
4. The headquarters business object stores the data (or takes whatever other
appropriate action on the request) if possible.
Some examples
Let's say your client phones and says
something like, "We need a system that will let some remote weather
stations collect meteorological data and then periodically upload the data
to the main office for analysis." Or, "We'd like to lower the
cost of monitoring the levels in the bulk tanks at all of our gas stations.
Right now, we have men drive around once a day to each one to take the level,
but we'd like to install a sensor that would take readings and then send
them into the office more frequently instead." Now we know we have
a tool in our toolbox that makes solving these problems relatively easy.
The "RSVP" scenario
In the second scenario, the field part
of the application is a peer with the headquarters part. The two parts share
responsibility for the data. This might be categorized as a truly "distributed"
application where the headquarters part perhaps provides a coordination
function, but otherwise is just another member of the team.
In this scenario, the field part wants to send a request to a business object
at headquarters, but it must get a positive response from headquarters before
relinquishing responsibility for the data. On the other hand, if the field
part gets a negative response from headquarters, it means that headquarters
declined to take responsibility for the data. The field must maintain responsibility
for the object to preserve the overall integrity of the application. The
logical design for such a system is shown in Figure
4.
As you can see, the "request" part of the scenario is a "lob."
But now the field system needs to add some mechanism to "remember"
requests that have been made and to determine what to do if a negative response
is received from headquarters. The details are filled in with a physical
design, as shown in Figure
5.
The "response" part of the RSVP scenario is symmetrical to the
request part. The headquarters monitor formulates a response message based
on the outcome of the call that it makes to the headquarters business object.
If the call succeeds, the monitor generates a positive response. If, on
the other hand, the call fails (for instance, the headquarters business
object raises an error), the monitor generates a negative response.
The following numbered steps match the numbers in Figure
5:
1. As in the lob scenario, the field application calls "AddItem"
on the local business object interface.
2. In the RSVP scenario, the field part's local business object takes responsibility
for the data by retaining it locally. Note that in this scenario, the local
business object interface is a superset of the headquarters business object
interface; the local business object interface has a couple of additional
methods on it that support the "transfer" functionality.
3. At some point, the application (or possibly the business object itself)
decides that it wants to "transfer" responsibility for some data
to headquarters, so it invokes the "TransferItem" method on the
field-side business object.
4. The headquarters monitor object reads a message from the request queue.
5. The headquarters monitor object invokes the "AddItem" method
on the headquarters business object's interface.
6. The headquarters business object either assumes responsibility for the
data (by successfully executing the method) or declines to accept responsibility
for the data (by raising an error).
7. The headquarters monitor object formulates the appropriate message and
sends it to the response queue.
8. The field-side monitor object reads the response message.
9. The field-side monitor invokes the "UpdateTransferStatus" method
on the field business object with a parameter value that indicates success
or failure of the "remote method invocation."
10. The field-side business object updates its durable state with the results
of the request.
An example
I recently worked on a project for
an insurance agency. Policy information was created in field offices. Some
processing could be done in the field, too, but in some cases, workload
could be transferred to a headquarters office if the field office got too
busy. The headquarters could either accept responsibility for the processing,
decline responsibility, or transfer responsibility to a field office that
wasn't busy. A perfect application for "remote method invocations"
via MSMQ!
The sample application
The accompanying sample application (available
in the accompanying Download
file) is an implementation of the physical design
depicted in Figure
5. On the field side, we have a simple application
that lets us test the implementation. The application has three command
buttons:
- Add to Local
inserts whatever data is entered into the text box (labeled "sValue")
into the local data store.
- Refresh List
displays the rows in the local data store. The rows are filtered based
on the selected radio button in the "Include" frame:
* Current Only means that only rows with false
values in both the "Transfer Requested" and "Transfer Confirmed"
columns will be displayed.
* Current & Pending means that rows with false in
the "Transfer Confirmed" column will be displayed without regard
to the value in the
"Transfer Requested" column.
* All means that no filtering will be applied.
- Transfer
attempts to transfer the selected row to headquarters.
We start by adding a couple of items to the field-side data store.
Figure
6 shows the application
immediately after the Add to Local button was pushed. The grid at
the bottom of the form shows a view of the information in the data store.
In Figure
7, we've requested a transfer of item #1. Notice
that the business object has set the "Transfer Requested" value
to true, but headquarters hasn't acknowledged the request yet ("Transfer
Confirmed" = false).
Now, in Figure
8, we've requested transfer of both items.
Figure
9 shows the GUI for
the headquarters monitor shortly after a physical connection was established
with the field. Headquarters will decline to accept responsibility for item
#1 (id = 58). Why? In this case, it's because the headquarters business
object was coded to reject any request whose id was evenly divisible. In
real life, it could be because of a variety of reasons, such as a business
rule violation, a lack of storage space, an error that happened during processing,
and so forth.
Now, back on the field side, we can look at the GUI for the field-side monitor
(see Figure
10). It tells us that the response messages have
been received from headquarters, and, presumably, the business object has
been called, once for each received message, to update the transfer status
for each of the items.
Finally, we look at our application again and see that item #2 was successfully
transferred, but item #1 is our responsibility again (see Figure
11).
The code
Surprisingly, there's not really a
lot of code to look at -- MSMQ is pretty straightforward to program. Most
of the MSMQ-specific work is encapsulated in two helper classes, one for
incoming and one for outgoing queue messages.
The field business object
Let's start on the field side. If we
want to transfer an item, the TransferItem method on the field-side
business object is invoked. In the implementation of TransferItem,
we create an instance of the helper class, clsQueueOut:
Dim clsQueueOut As clsQueueOut
Set clsQueueOut = New clsQueueOut
|
Then we set a few properties on the clsQueueOut instance:
clsQueueOut.AppSpecific = lID
clsQueueOut.QueueName = mcsRequestQueue
clsQueueOut.AppendOperationName "TRANSFER"
clsQueueOut.AppendArg sValue
|
We tell the clsQueueOut instance to send the message, and finally, we deallocate
the clsQueueOut instance:
clsQueueOut.SendMessage
Set clsQueueOut = Nothing
|
clsQueueOut
Most of the interesting MSMQ stuff
in clsQueueOut is in the SendMessage method. MSMQ provides an automation
interface to the VB programmer that includes a pretty simple object model.
As you can see, we're using MSMQQueue and MSMQMessage objects.
Dim qSend As MSMQQueue
Dim qMsg As MSMQMessage
|
First, we open the MSMQQueue (represented by the qSend instance):
Set qSend = OpenQueue(msQueueName, MQ_SEND_ACCESS)
|
Then we create an instance of an MSMQMessage and set some of its
properties:
Set qMsg = New MSMQMessage
With qMsg
.Label = mcsAppName & " " & Date & " " & Time
.Body = MakeBody()
.Delivery = MQMSG_DELIVERY_RECOVERABLE
.AppSpecific = mlAppSpecific
|
Finally, we send the message to the queue:
and clean up:
qSend.Close
ClearArgs
End If
|
There are a few things to note:
- The message Body can be any of the "simple
Variant" types, or any object that supports the IPersist COM interface.
We're going to use a string. Our MakeBody function returns a string composed
of the method name (that we want to invoke on the remote business object)
concatenated with each of the parameters for the method.
- The message Label will be displayed in the MSMQ
Explorer.
- As its name implies, the AppSpecific property
can be used for any application-specific data. That is, MSMQ just passes
this information around for us and doesn't care what's in it. We use it
to identify the record from the field side. Unfortunately, this property
is defined as a Long, so its use is somewhat constrained.
The headquarters monitor
The headquarters monitor is the application
that's really at the core of the RSVP scenario. It's responsible for five
things:
1. monitoring the request queue for messages from the field;
2. unpacking the message;
3. invoking the appropriate method on the business object;
4. displaying a visual indicator of activity; and
5. invoking the appropriate field-side business object method.
As we can see from the following declarations, which are in frmMain, the
headquarters monitor uses both types of helper classes as well as our "real"
business object:
Private moHQBusiness As HQBusiness.clsHQBusiness
Private WithEvents moQin As clsQueueIn
Private moQout As clsQueueOut
|
One important thing to notice is the use of the WithEvents keyword
for the clsQueuIn instance. Visual Basic automatically adds an empty event
handler (a private sub) to our code for each event that the clsQueueIn instance
might raise. In this case, there only two: Arrived and ArrivedError.
In this sample, we're only interested in the Arrived event. This
event will notify our form when a message has been put in the queue to which
the clsQueueIn instance is "attached". The event mechanism helps
us with responsibility #1. Here's the code in our Arrived event handler:
Private Sub moQin_Arrived(ByVal queue As Object,_
ByVal Cursor As Long)
On Error GoTo EH
Const csProcName As String = "moQin_Arrived"
Dim qMsg As MSMQMessage
|
First, we fetch the message out of the queue:
Then, we process it. ProcessMessage is a private method that unpacks
the method name and arguments that we stuffed into the MSMQMessage body
property back on the field side (responsibility #2), then invokes the appropriate
method on our business object (responsibility #3).
Next, we display the results on the GUI (responsibility #4):
List1.AddItem Date & " " & Time & ": Transferred _
" & CStr(qMsg.AppSpecific)
|
And, finally, we send a message back to the field side (responsibility #5).
Remember that the field side is waiting for either a positive or a negative
acknowledgement so that it can decide whether it's still responsible for
the data. In this case, we've successfully invoked the business-side business
object (no error was raised), so we know that the business-side object has
assumed responsibility for the data and we can send back a positive acknowledgement:
SendResponseMessage "CONFIRM", qMsg.AppSpecific
Set qMsg = Nothing
Exit Sub
EH: 'your error handling code goes here
|
On the other hand, if an error was raised while executing moQin_Arrived,
we know that our headquarters business object has not taken responsibility
for the data. We must send a negative acknowledgement back to the field:
'send decline message back to field
SendResponseMessage "DECLINE", qMsg.AppSpecific
'display result on GUI
List1.AddItem Date & " " & Time & _
": ERROR while processing " & CStr(qMsg.AppSpecific)
Set qMsg = Nothing
End Sub
|
Conclusion
Two physically separate applications might
need to collaborate to provide some functionality. The collaboration might
be in one direction only (the "lob" form) or bi-directional (the
"RSVP" form). This article has illustrated how to implement this
collaboration via "remote method invocations" between custom business
objects. MSMQ is used as the transport infrastructure for the remote method
invocations.
Download MSMQCODE.exe
R.L. Parker is a Microsoft Certified Solution Developer (MCSD) and Master
Technical Lead at DB Basics, Inc. in Raleigh, N.C, who specializes in mentoring
and custom development of mission-critical database applications including
data-driven Web sites. rlp@dbbasics.com.
Sidebar: Quick Intro to MSMQ
Microsoft Message Queue (MSMQ) is a technology
that enables loosely coupled inter-application communication. The applications
involved are loosely coupled in time (the message sender and the
message receiver[s] don't have to be online at the same time). Another way
to say this is that the MSMQ protocol is asynchronous and connectionless.
The applications are also loosely coupled in identity (the message
sender doesn't have to identify the intended recipient of the message, nor
does the message receiver necessarily know or care about the sender of the
message). The semantics of inter-application communication via MSMQ is more
like event semantics than procedure-call semantics.
While getting data from application A to application B is the core of what
MSMQ does, it supports many other features. A full enumeration and discussion
of them would require a book -- several of which exist. That said, here's
a list of some of the more important features:
- transactions can be used to guarantee once-only
and in-order delivery or to wrap other work, such as database updates,
inside the same transaction as sending or receiving a message
- message prioritization
- message tracing
- acknowledgement when a message is delivered or
is deemed undeliverable
- message authentication and encryption
- system security based on Access Control Lists
(ACLs)
Interoperability with other message queue systems, such as IBM MQSeries,
is possible via MSMQ "connector servers" such as Level 8 Systems'
FalconMQ Bridge (www.level8.com) -- part of which Microsoft has licensed to include in
future versions of MSMQ. (The fact that a queue is in a "foreign"
system is transparent to an application on the MSMQ side.) Microsoft distributes
MSMQ free as part of the Windows NT 4.0 Option Pack.
Jargon
- Message --
An MSMQ message is some data and related properties. Don't confuse this
kind of message with inter-personal messages (e-mail). The data in an MSMQ
message is formatted in a manner that's mutually agreed upon by the participating
applications. For example, the message might contain comma-delimited raw
data from a data-gathering application; the receiving application will
parse the message and insert the data into a database. The message data
is stored in the message "body," but messages have several other
interesting and useful properties.
Besides raw data, the message
body may contain a serialized object (for example, the state of an object
whose class implements IPersistStream). Since VB 6.0 supports "persistable"
classes in ActiveX DLLs and EXEs, VB developers now have this interesting
possibility available to them.
- Queue --
A queue holds messages. A given queue physically resides on a single computer.
Application queues can be created administratively or programmatically.
System queues are ones that are automatically maintained by MSMQ. For example,
messages that can't be delivered are sent to the "dead letter"
system queue. If requested by the sending application, messages can be
copied to a "journal" system queue as they pass through the MSMQ
infrastructure.
- Site -- A
site is a collection of computers that are connected by a reliable and
fast physical connection.
- Server --
Servers host queues and provide intra- and inter-site message routing.
- MQIS -- The
Message Queue Information Store is a distributed database that MSMQ maintains
about all the various servers, sites, queues, and so forth in the enterprise.
- Client --
If a computer isn't an MSMQ server, it must be configured as an MSMQ client
in order to send/receive MSMQ messages. There are two flavors of MSMQ client:
Independent clients can host queues but don't do any of the MQIS
functions; dependent clients must be physically connected to an
MSMQ server to send/receive messages. In contrast, machines that are configured
as independent clients can send a message even if the target queue is on
a machine that's physically unavailable.
Administration
MSMQ is administered through its own Explorer
interface (see Figure
1a). Servers and queues are listed in a tree view.
By right-clicking on a server or queue in the tree view, the object's properties
can be examined and/or set (see Figure
2a).
Programming
MSMQ presents a rich object model to the
VB programmer. Here's a partial list of the ActiveX exposed by MSMQ:
- An MSMQMessage object encapsulates the
inter-application message. The data is assigned to the Body property.
The body can contain a string, a byte array, or any numeric, date, or currency
type that a variant can contain. As I mentioned, it can also contain the
state of any ActiveX object that implements IDispatch and IPersist (IPersistStream
or IPersistStorage). MSMQMessage objects have many properties, but only
one method: Send.
- An MSMQQueue object provides a "cursor"
on the messages in the MSMQ queue. It has methods that enable you to "peek"
at (Peek, PeekCurrent, PeekNext) or read (Receive, ReceiveCurrent)
messages in the queue. It also has a method (EnableNotification)
that enables the queue to raise an event to your application when a message
is delivered to the queue.
- An MSMQQuery object allows you to query
MQIS for existing public queues based on queue properties such as type
or label (user-friendly identifier). The results are returned in an MSMQQueueInfos
object, which is a collection of MSMQQueueInfo objects that matched the
search criteria.
- An MSMQQueueInfo has properties and methods
that let you programmatically administer queues. Some of its properties
include the following:
* PathName is required when creating a queue; composed of a
machine name, and optional "private" designator, and a queue
name
* IsTransactional is a read-only property whose value was set
when the queue was created; transactional queues will accept only transactional
messages
Some of its methods include Create, Delete, and Update.
References
Msmqadm.hlp is the administrator's manual
delivered with MSMQ. Msmqsdk.hlp is the programmer's manual delivered with
the MSMQ SDK. I've found both of these manuals to be very useful.
Summary
MSMQ makes a lot of functionality available
to the VB programmer. Though it isn't the answer to every problem, it's
a good tool to have in your toolbox.