Routing Service and Content Based Routing
Routing Service
In Service Oriented Applications there are many cases we need an intermediate software between clients and services that bypasses the communication between them. The intermediate software is typically called as a router. Some of the well-known examples where we need a router are protocol mapping (ex. the service is exposed to tcp while the clients expects http), load-balancing (ex. distributing messages to services running in different servers in a round-robin fashion), content-based routing (routing messages to different services based upon the message content) etc.
WCF 4.0 ships with a new service called Routing Service that makes developers life so easy by separating out the routing logic from the clients and services. The nice thing about the Routing Service is it is so easily pluggable and configurable. This Routing Service is available under the new assembly System.ServiceModel.Routing.
In this article I’m going to describe about how we can achieve content-based routing using the WCF Routing Service.
Content-Based Routing
In content-based routing, based upon the parameter(s) in the SOAP message the message is routed to a particular service endpoint. WCF comes with a set of built-in message filters that helps to filter the incoming SOAP messages based upon the SOAP action or endpoint or even through a particular parameter in the SOAP header or body. Some of the built-in message filters are MatchAllMessageFilter, ActionMessageFilter, XPathMessageFilter etc.
We can easily create our own filters by deriving from the abstract class MessageFilter and overriding couple of methods. Let’s see an example of doing content-based routing through XPathMessageFilter.
Example
Let’s assume we have a Pizza Order Processing Company named "Papa Louie" and the company contains two services that takes orders. One service takes order for itallian pizzas (ItallianPizzaService) and the other service takes order for non-itallian pizzas (GeneralPizzaService). Since both the services does the same operation they implement the same contract IPizzaService.
We are going to create a router that receives all the incoming SOAP messages and filter them based upon a parameter called (PizzaType) in the message body. If the value of PizzaType is itallian then those orders are routed to ItallianPizzaService else they are routed to the GeneralPizzaService.
Let’s see how we can implement this.
Contracts
The services are quite simple they provide a single operation called OrderPizza that takes an order and write a message to the console.
[ServiceContract(Namespace = "http://papalouie.com/contracts")] interface IPizzaService { [OperationContract] void OrderPizza(Order order); } class ItallianPizzaService : IPizzaService { public void OrderPizza(Order order) { Console.WriteLine("Order from {0} for {1} is received.", order.OrdererName, order.PizzaName); } } class GeneralPizzaService : IPizzaService { public void OrderPizza(Order order) { Console.WriteLine("Order from {0} for {1} is received."); } }
The Order class contains few properties like OrdererName, PizzaName, PizzaType, DeliveryAddress and DeliveryTime.
[DataContract] class Order { [DataMember] public string OrdererName { get; set; } [DataMember] public string PizzaName { get; set; } [DataMember] public string PizzaType { get; set; } [DataMember] public string DeliveryAddress { get; set; } [DataMember] public string DeliveryTime { get; set; } }
Both the services are exposed for http transport and they are running in addresses http://localhost:8000/papalouie/itallianpizzaservice and http://localhost:9000/papalouie/generalpizzaservice.
Router
Although the router is not aware of the clients it should know about the service endpoints where the messages has to be routed based on the filters. The following are the configurations we are going to do in the router side.
- Create an endpoint for the routing service
- Create a service behavior for the routing service
- Specify the pizza services endpoints
- Create filters and filter-tables
Let’s do one by one.
1. Create an endpoint for the routing service
<service name="System.ServiceModel.Routing.RoutingService" behaviorConfiguration="routerConfig"> <endpoint address="http://localhost:8080/papalouie/router" binding="basicHttpBinding" contract="System.ServiceModel.Routing.IRequestReplyRouter" name="reqReplyEndpoint" /> </service>
Our router is running in address http://localhost:8080/papalouie/router using basicHttpBinding. One important thing to note down is the contract IRequestReplyRouter. The routing service is available with a set of router types and discussing about them is beyond the scope of this article. For request-reply channels typically IRequestReplyRouter is used.
2. Create a service behavior for the routing service
To configure the routing for our routing service we have to create a service behavior.
<serviceBehaviors> <behavior name="routerConfig"> <routing filterTableName="routingTable" routeOnHeadersOnly="false" /> </behavior> </serviceBehaviors>
In the service behavior we have to specify the filterTableName for the routing. We’ll see about filter-tables when we create filters. What is that routeOnHeadersOnly? By default the filters can only read the parameters in the SOAP headers. In our example we want to route the messages based on the property in the SOAP body so to enable that we have to specify that property to false.
3. Specify the pizza order services endpoints
The messages received by the router will be directed to one or more endpoints. The services where the messages have to be routed are the clients to the router. So in our order processing system the ItallianPizzaService and GeneralPizzaService are the clients to the router and let’s specify their endpoints in the clients section.
<client> <endpoint name="ItallianPizzaService" address="http://localhost:8000/papalouie/itallianpizzaservice" binding="basicHttpBinding" contract="*" /> <endpoint name="GeneralPizzService" address="http://localhost:9000/papalouie/generalpizzaservice" binding="basicHttpBinding" contract="*" /> </client>
If you see the endpoints of the pizza services you notice that the contact is specified as "*" this is because the routing service doesn’t cares about the contract of the services it just route the SOAP message if they meet the filter criteria.
4. Create filters and filter-tables
Configuring filters and filter-tables are very important in router. I already told you about the message filters but what are filter-tables? Well they are the ones that match the filters with the service endpoints. In our example we have to create two XPath filters, one for filtering orders for itallian pizzas and the other for non-itallian pizzas. Below is the configuration.
<routing> <namespaceTable> <add namespace="http://papalouie.com/contracts" prefix="o"/> </namespaceTable> <filters> <filter name="itallian" filterType="XPath" filterData="//o:PizzaType = 'Itallian'"/> <filter name="nonitallian" filterType="XPath" filterData="//o:PizzaType != 'Itallian'"/> </filters> <filterTables> <filterTable name="routingTable"> <add filterName="itallian" endpointName="ItallianPizzaService" /> <add filterName="nonitallian" endpointName="GeneralPizzService" /> </filterTable> </filterTables> </routing>
The <routing> section consists of three sections <namespaceTable>, <filters> and <filterTables>. All the filters that are created for the routing are located in the <filters> section. For each filter we have to specify the name and the type. The filterData is the XPath condition that is passed to the filter’s constructor. To avoid specifying complete namespace in the XPath query we can use short-hand prefixes by the help of <namespaceTable> section. Since our contracts have the namespace http://papalouie.com/contracts we have created a short-hand prefix(o) for that.
Here’s the complete configuration that we have done in the routing service.
<system.serviceModel> <services> <service name="System.ServiceModel.Routing.RoutingService" behaviorConfiguration="routerConfig"> <endpoint address="http://localhost:8080/papalouie/router" binding="basicHttpBinding" contract="System.ServiceModel.Routing.IRequestReplyRouter" name="reqReplyEndpoint" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="routerConfig"> <routing filterTableName="routingTable" routeOnHeadersOnly="false" /> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <client> <endpoint name="ItallianPizzaService" address="http://localhost:8000/papalouie/itallianpizzaservice" binding="basicHttpBinding" contract="*" /> <endpoint name="GeneralPizzService" address="http://localhost:9000/papalouie/generalpizzaservice" binding="basicHttpBinding" contract="*" /> </client> <routing> <namespaceTable> <add namespace="http://papalouie.com/contracts" prefix="o"/> </namespaceTable> <filters> <filter name="itallian" filterType="XPath" filterData="//o:PizzaType = 'Itallian'"/> <filter name="nonitallian" filterType="XPath" filterData="//o:PizzaType != 'Itallian'"/> </filters> <filterTables> <filterTable name="routingTable"> <add filterName="itallian" endpointName="ItallianPizzaService" /> <add filterName="nonitallian" endpointName="GeneralPizzService" /> </filterTable> </filterTables> </routing> </system.serviceModel>
Testing
The attached sample consists of five projects. 1. Contracts class library 2. ItallianPizzaService console host 3. GeneralPizzaService console host 4. Router service 5. Client. You have to run the services and router first before running the client.
Let’s run the client, order some pizzas and make Papa Louie happy ☺
var factory = new ChannelFactory<IPizzaService>(new BasicHttpBinding(), "http://localhost:8080/papalouie/router"); var channel = factory.CreateChannel(); var order1 = new Order { OrdererName = "Mark Pizz", PizzaName = "Pizza Margherita", PizzaType = "Itallian", DeliveryAddress = "455 Larkspur Dr. California Springs, CA 92926", DeliveryTime = "10:00 AM" }; channel.OrderPizza(order1); var order2 = new Order { OrdererName = "Pizza Hizza", PizzaName = "Chicago Veg", PizzaType = "Chicago", DeliveryAddress = "23 Mark Street, CA 34545", DeliveryTime = "11:00 AM" }; channel.OrderPizza(order2); var order3 = new Order { OrdererName = "Pizza Hizza", PizzaName = "Pizza al Prosciutto", PizzaType = "Itallian", DeliveryAddress = "23 Mark Street, CA 34545", DeliveryTime = "12:00 AM" }; channel.OrderPizza(order3);
So we have placed three orders. The first and third orders are for itallian type pizzas so they are received by the ItallianPizzaService and displayed in its console.
The second order is for type non-itallian pizza and it is received by the GeneralPizzaService and displayed in its console.
Summary
In this article I described about one of the new features in WCF 4.0, the Routing Service. We saw about configuring the routing service, specifying endpoints and creating filters/filter-tables. In upcoming articles I’ll write more about creating custom filters, auditing, fail-over services, load balancing etc. etc. so stay tuned!