WCF offers a clean abstraction for remote procedure calls that hides the complexity of the underlying communication technologies. Ideally one could design an interface that works regardless of the transport technology one ultimately decides to use.
Unfortunately, this does not always work because the abstraction is [
leaky], i.e. details of the implementation can affect the higher-level design. In WCF this is most obviously illustrated by the limitations imposed by message queues. Specifically, all methods must be [
one-way http://msdn.microsoft.com/en-us/library/ms733035.aspx], which means the return type of all remote methods must be
void. If a design requires a normal method with a return type, then developers must implement the
request-reply messaging design pattern. This allows designers to offer a familiar API design (methods that return values) while still making use of the reliability and availability features of a message queue. In effect, developers must manually write another layer on top of WCF to seal those leaky abstractions.
In this example we want to implement IMyApi shown below using the NetMSMQBinding. Of course we can’t implement it directly because the API is not a one-way method. Instead, we’ll need to implement two-way communication using a pair of one-way methods. We’ll need a one-way method from the client to the server, and another from the server back to the client to handle the response. Obviously this means that both the client and the server will need to host services to process incoming messages from their respective queues. We’ll need to create a pair of private queues in MSMQ called “request” and “response” using the Computer Management console. We’ll also define a pair of service interfaces for both queues, as shown below.
// Primary client interface
interface IMyAPI {
int RequestReply(int val) ;
}
[ServiceContract]
interface IServer {
[OperationContract(IsOneWay=true)]
void Request(MessageHeader header, int val);
}
[ServiceContract]
interface IClient {
[OperationContract(IsOneWay=true)]
void Reply(MessageHeader header, int answer);
}
Unfortunately, many useful message properties available for MSMQ (see System.Messaging.Message class) have been hidden in WCF. We need a way to match the responses to the original requests since messages can arrive out of order (especially in a concurrent application). To implement this pattern we need a CorrelationID property that holds a Guid to match responses to requests. If we also want to support many clients hitting a single server, we’ll need the ResponseQueue property to tell the server to which queue to send the response. Both of these are implemented in a MessageHeader class. In a real application, you may add a few other properties to help process messages.
[DataContract]
public class MessageHeader {
[DataMember]
public Guid CorrelationId { get; set; }
[DataMember]
public EndpointAddress ResponseQueue { get; set; }
public MessageHeader(Guid id, EndpointAddress respq) {
CorrelationId = id;
ResponseQueue = respq;
}
}
Now we can implement the server object to process messages on the request queue and fire responses back to the client. Implementing the service is fairly easy, but we are forced to implement the response code manually because the client’s ResponseQueue may change. In this example we’ll implement a simple console application to host the server-side code below. Notice that the Request method, which is executed for each new message on the response queue, creates a Channel to the client to send back a message. Since there may be multiple clients, the code needs to construct the channels dynamically.
class Server : IServer {
public void Request(MessageHeader hdr, int msg)
{
Console.WriteLine("ID: {0}, Response Queue: {1}, Message: {2}", hdr.CorrelationId, hdr.ResponseQueue, msg);
Thread.Sleep(500); // pretend to do work
var bin = new NetMsmqBinding(NetMsmqSecurityMode.None);
var chan = new ChannelFactory<IClient>(bin, hdr.ResponseQueue);
var proxy = chan.CreateChannel();
proxy.Reply(hdr, msg);
chan.Close();
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Running...");
using (var host = new ServiceHost(typeof (Server)))
{
var bin = new NetMsmqBinding(NetMsmqSecurityMode.None);
host.AddServiceEndpoint(typeof (IServer), bin, "net.msmq://localhost/private/request");
host.Open();
Console.ReadLine();
}
}
}
The client side code is a bit more complex even though it is essentially doing the same thing as the server. The problem is that the requests and responses are disconnected. We need to write a proxy object that implements IMyAPI for the client. Behind the scenes, this class will send a message to the server and then wait for a response. The response will execute the code below, which merely stuffs the response message into a dictionary. Since the responses are likely arriving on a different thread, the table must be a ConcurrentDictionary implemented as a Singleton object.
class Client : IClient
{
public static ConcurrentDictionary<Guid,int> Table = new ConcurrentDictionary<Guid,int>() ;
public void Reply(MessageHeader hdr, int msg)
{
Console.WriteLine("ID: {0}, Response Queue: {1}, Message: {2}", hdr.CorrelationId, hdr.ResponseQueue, msg);
Table[hdr.CorrelationId] = msg;
}
}
Remember that this is not the client we really want to use. Instead, we need to implement the IMyAPI interface with a proxy object that sends a message to the server and waits for the client to receive a response. In the code below, we are using the same ClientBase abstract class that WCF uses when it generates a proxy object in Visual Studio. However, we are exposing the IMyAPI instead of the one-way Request method. The constructor for this class accepts the address of the server’s queue and establishes a channel to the server. The first few lines of the RequestReply method simply constructs a MessageHeader and calls the Request method on the base class’s Channel property.
class MyAPI : ClientBase<IServer>, IMyAPI
{
static readonly NetMsmqBinding bin = new NetMsmqBinding(NetMsmqSecurityMode.None);
public int RequestReply(int val) {
var id = Guid.NewGuid();
var addr = new EndpointAddress("net.msmq://" + Dns.GetHostName() + "/private/response");
var hdr = new MessageHeader(id, addr) ;
Channel.Request(hdr, val); // Send message to server
if (SpinWait.SpinUntil(() => Client.Table.ContainsKey(id), 2000)) {
int answer;
Client.Table.TryRemove(num, out answer);
return answer;
} else {
Console.WriteLine("Couldn't find the key: " + id);
return -1;
}
public MyAPI(EndpointAddress addr) : base(bin, addr) {}
}
The second half of the RequestReply method waits for an answer. It uses a SpinWait structure to repeatedly check the dictionary to see if a response has been added for this Guid. It will wait for up to 2 seconds, after which it will return a failure code (-1). If it does find a response in the table, it removes it and returns it.
The objective of this article is to expose a simple API to the user, but use message queues as the transport technology. The code below, which runs on the client side, demonstrates that we’ve achieved our goal. The first few lines are just to host the Client object to listen to the response queue. The real magic happens when the user simply calls a new MyAPI object and gets the response. When this program is executed the responses will usually arrive out-of-order, usually due to the way the Parallel class schedules execution of the loop. If this client is run on multiple machines, the server will send responses to the correct client.
static void Main(string[] args)
{
using (var host = new ServiceHost(typeof(Client)))
{
var bin = new NetMsmqBinding(NetMsmqSecurityMode.None);
host.AddServiceEndpoint(typeof (IClient), bin, "net.msmq://localhost/private/response");
host.Open();
var proxy = new MyAPI(); // MAGIC!!
Parallel.For(0, 10, (j) => {
var answer = proxy.RequestResponse(j);
Console.WriteLine("Got a response: " + answer);
});
}
Console.WriteLine("Finished.");
Console.ReadLine();
}
While this code is just a simple sketch of the request-reply message pattern, it does provide enough of a skeleton to build a more robust and functional implementation. For example, if a response does not arrive in time an implementation may return a nullable type to indicate failure. It might also provide another method (EndRequestReply) to fetch the answer if it arrives very late. What if the answer arrives late and the user doesn’t want it anymore? You need a way to clean the Table or it will suffer a memory leak. Another problem is that the client and server code explicitly start the ServiceHost. A better solution is to let Windows Activation Service start the WCF services so users can just write normal code. There are many ways to improve this code to make it more useful.
More broadly, however, this example illustrates that you can combine clients and servers in different ways to construct more useful design patterns with message queues. The book “Enterprise Integration Patterns” by Gregor Hohpe and Bobby Woolf describes a large suite of patterns that make use of messaging systems. WCF, like any library really, is merely a tool. The more important step is to learn how to use these libraries to build more complex, robust, reliable and scalable systems. That’s the key difference between a code monkey and a developer. The former knows the right methods to call, but the latter can use those methods to build amazing software architectures.