Enterprise Service Buses (ESBs) Drive SOA Adoption Part 5 - Itinerary Based Routing
Part 5 – Itinerary Based Routing Message Pattern in Neuron
Introduction
In the last installment, I discussed the Publish-Subscribe Message Exchange Pattern. We looked at classical definitions of the MEP, and then looked at how this pattern is the very base of the internal architecture of Neuron ESB. It does turn out that there are a great variety of MEPs, and yes, they are again cataloged in Hohpe and Woolf’s Enterprise Application Integration book and the PAG Integration Patterns book. Another MEP and core function of an ESB like Neuron is the ability to route messages from one place (node) to another.
Message Router Pattern Defined
“Insert a special filter, a Message Router, which consumes a Message from one Message Channel and republishes it to a different Message Channel channel depending on a set of conditions.
The Message Router differs from the most basic notion of Pipes and Filters in that it connects to multiple output channels. Thanks to the Pipes and Filters architecture the components surrounding the Message Router are completely unaware of the existence of a Message Router. A key property of the Message Router is that it does not modify the message contents. It only concerns itself with the destination of the message.”
Sounds pretty straightforward I would say. The “filter” changes the “destination” of the “message” depending on some set of conditions. As you might expect, there are other patterns that are variants “depending on a set of conditions.” One of the most popular is the Content-Based Router which routes based on message content. A Message Filter is a special kind of Router that eliminates undesired messages. We’ll cover that guy later in the series as Message Filters are a part of the extensive array of Transformation services in Neuron.
One of the more interesting routing patterns, and the one we will implement with Neuron, is the Routing Slip or Routing Table pattern. This pattern addresses
“How do we route a message consecutively through a series of processing steps when the sequence of steps is not known at design-time and may vary for each message?”
The Pattern says,
“Attach a Routing Slip to each message, specifying the sequence of processing steps. Wrap each component with a special message router that reads the Routing Slip and routes the message to the next component in the list.
We insert a special component into the beginning of the process that computes the list of required steps for each message. It then attaches the list as a Routing Slip to the message and starts the process by routing the message to the first processing step. After successful processing, each processing step looks at the Routing Slip and passes the message to the next processing step specified in the routing table.”
Routing and WCF
Again, as I have stated before, WCF is just a messaging framework, low-level plumbing and does not natively implement these Routing patterns just like it does not implement Publish-Subscribe. It turns out, that implementing message routing with raw WCF is quite hard.
For many of the gory details, I refer you to Michele Bustamante’s two MSDN articles on this subject.
She also has an extensive set of code examples on her blog that implement all sorts of routers:
As she states, the heart of writing a Router with WCF is a contract like so:
[ServiceContract(Namespace = "http://www.thatindigogirl.com/samples/2008/01")]
public interface IRouterService
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message ProcessMessage(Message requestMessage);
}
Because the Action and ReplyAction are set to “*”, the channel dispatcher will send any messages not mapped to a specific operation to that catch-all operation regardless of the ActionHeader value. A very simple case implementation ProcessMessafe constructs a client channel (or proxy) with ChannelFactory<T> and uses this proxy to forward messages to a particular service endpoint, returning any responses.
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class RouterService : IRouterService {
public Message ProcessMessage(Message requestMessage) {
using (ChannelFactory<IRouterService> factory =
new ChannelFactory<IRouterService>("serviceEndpoint")) {
IRouterService proxy = factory.CreateChannel();
using (proxy as IDisposable) {
return proxy.ProcessMessage(requestMessage);
}
}
}
}
This part is simple enough, but as Michele details in her article, an un-expected consequence is that the To-header is altered before the message is sent to the service. This is because, by default, the proxy will use the logical address from its endpoint configuration to set the To header for outgoing messages – even of a raw message already has a To header.
There a bunch of ways to “correct” this with WCF that she speaks of: using a ClientViaBehavior, configuring the listenUri attribute for endpoints so that the logical address if the service is that of the router while the physical address is specific to the service, or using services use a logical address that is of type URI that is not tied to the router or service, and then manually tell clients the physical address to send messages to since it will not be part of the metadata.
The WCF “Intermediary Router” sample included with the product samples uses two services and a routing table. Both the EchoService and the CalculatorService use the ListenUri property, specified as one of the options, to set the URI that the endpoint is listening on. This looks like:
<!-- Echo service application endpoint. -->
<endpointaddress="http://localhost:8000/services/soap12/text"
listenUri="service"
contract="Microsoft.ServiceModel.Samples.IEchoService"
binding="wsHttpBinding"
bindingConfiguration="ServiceBinding" />
By now you should be rolling your eyes and recalling what I have already said in this series: this is way too much “plumbing work” and “WCF is too low-level” a framework to build real messaging and SOA applications. We need a higher-level abstraction: that supplied by a Service Bus.
Implementing Itinerary Routing with Neuron
Neuron hides all of this WCF complexity and implements all the plumbing, leaving you with essentially one line of code on the sending side:
publisher.Send("ContactReceiver@Contacts.New,ContactReceiver2@Contacts.New", contact, SendOptions.Routed);
An overload of the Send API call allows us to set the SendOptions to “Routed.”
As I said, the sample I am showing here is that of the Routing Slip pattern. In Itinerary routing, the sender specifies a routing slip of destinations. Each recipient of a message routes the message on to the next destination on the itinerary. Each destination on the itinerary is either a topic or subscriber (specified as partyId@topic).
The complete sender is:
static void Main(string[] args)
{
try
{
Console.WriteLine("Initializing sender");
using (Publisher publisher = new Publisher())
{
publisher.Connect();
Console.WriteLine("Press <ENTER> to start sending");
Console.ReadLine();
Contact contact = CreateContact();
Console.WriteLine("Sending contact");
publisher.Send("ContactReceiver@Contacts.New,ContactReceiver2@Contacts.New", contact, SendOptions.Routed);
}
}
catch(Exception ex)
{
Console.WriteLine("Exception: " + ex.ToString());
}
finally
{
Console.WriteLine("Press <ENTER> to shut down.");
Console.ReadLine();
}
}
private static Contact CreateContact()
{
Contact contact = new Contact();
contact.ContactType = ContactType.Customer;
contact.LastName = "Smith";
contact.FirstName = "John";
contact.Street = "100 Main St";
contact.Street2 = "";
contact.City = "Los Angeles";
contact.Region = "CA";
contact.PostalCode = "99123";
contact.Country = "USA";
contact.Phone = "";
contact.Email = "";
return contact;
}
}
[Serializable]
public enum ContactType { Other, Customer, Vendor, Personal }
[Serializable]
public class Contact
{
public ContactType ContactType;
public string LastName;
public string FirstName;
public string Street;
public string Street2;
public string City;
public string Region;
public string PostalCode;
public string Country;
public string Phone;
public string Email;
}
}
Now, the first Receiver uses the Subscriber.Route call:
public static void OnReceive(object sender, MessageEventArgs e)
{
messageCount++;
Console.WriteLine("Received messsage " + messageCount.ToString());
if (e.Message.Header.BodyType.Equals("Contact"))
{
Contact contact = e.Message.GetBody<Contact>();
DisplayContact(contact);
// Add phone and email to contact.
Console.WriteLine("Adding phone and email data to contact");
contact.Phone = "555-123-4567";
contact.Email = "john.smith@acme.com";
Console.WriteLine("Routing message");
subscriber.Route(e.Message, contact);
}
}
And, our “final” destination, simply writes out the contact:
public static void OnReceive(object sender, MessageEventArgs e)
{
messageCount++;
Console.WriteLine("Received messsage " + messageCount.ToString());
if (e.Message.Header.BodyType.Equals("Contact"))
{
Contact contact = e.Message.GetBody<Contact>();
DisplayContact(contact);
}
}
In reality, Neuron is doing all the work, and we used 2 lines of code, one in the sender and one in the first “receiver” or Intermediary to route the message.
It is a fairly simple task to modify to route by content and so forth. I think you will agree again that it is much more cost effective to work at this level.
Where Are We?
In this article, we started to implement messaging applications, specifically Message Routers. We first turned, as usual to the Hohpe and Woolf book to look at the Message Router pattern. We then looked at variants of routers. I then spent some time looking at what is involved in implementing routers with raw WCF. Then I showed you that it is built into Neuron and we needed two lines of code to do it.
Filed under: Neuron ESB
