Routing is one of the features added to WCF 4.0, which brings some quite nice features:
- Content-based load balancing
- Automatically use of fallback services
- Protocol bridging
- Masking of versioning issues
In an attempt to get familiar with Routing, I took the first scenario, where we will use content-based load balancing routing.
We have an OrderService which is accepting orders and processes these orders. From time to time we experience some peak loads, which somewhat worried our managers. They do not care all that much about latency and the speed of the order processing in general, except for our high stake clients (you know how some managers are). They value the orders of our high stake clients a lot higher then the general order and they want to make sure we can guarantee that the high stake orders do not experience anything if we would have some sort of peak in order submitting.
Obviously there are multiple ways to improve our scenario, like decent throttling, load balancing, queues and so forth. But a possible solution could aswell be provided with the WCF 4.0 routing.
Instead of having 1 OrderService like we have now, we will bring up another OrderService, so we will be having 2 OrderService’s. We will not simply load balance the requests to these 2 services round robin, but we will load balance the order request based on the content. We will route all orders with a total order price of $500 to the 2nd service we brought up. All the orders with a lower total price will still be routed to our current OrderService.
For pure load balancing purposes, you could also bring up multiple OrderServices and put a WCF Router in front of it, which distributes the requests round robin. You clients send the messages to the router, and the router just forwards to the processing services.
What does this mean:
In high peak loads, our current service will still have a lot of orders submitted and the latency in the processing of the order might increase a bit compared to the low load. However, even if we have a high peak time span, all the orders with more than $500 will be routed to our new and second OrderService. As the high stake orders are less common than the normal orders, our secondary OrderService will not have such a high load, making sure our high stake orders do not experience much latency even if we have a lot of low orders submitted. Previously all orders, high and low stake orders went through the same service and on a high peak, the high stake order would get queued behind the low order services that were submitted before the higher stake order.
In our scenario to go to through things a bit faster, we will just host 1 service with 2 endpoints instead of 2 services with 1 endpoint, and deliver the message according to the message content to 1 of those 2 endpoints. It’s basically the same as routing to 2 services, since both would be a different endpoint aswell.
Our solution setup:
We have 2 Console Applications. We used a client and one console application that will self-host our OrderService with 2 endpoints and which will host our RoutingService aswell, which will route the requests to the OrderService according to the message content. You will need to add System.ServiceModel to both Console applications and make sure your target framework is not set at the Client Profile framework.
Our Order class:
Our service contract:
Our service implementation:
Our SubmitOrder receives an Order and it writes at what endpoint it received this order and what the id and the total price of the order is.
To simulate this double OrderService, we will host the OrderService with 2 endpoints:
So we have an endpoint at http://localhost:8008/OrderService/Low and endpoint at http:localhost:8008/OrderService/High. It’s pretty obvious which order belongs where.
Now we need to set up and configure the WCF RouterService, which will do the routing to the OrderService according to a filter on the message content. To be able to work with the WCF routing you will need to add a reference to System.ServiceModel.Routing.
Our router configuration:
Notice the router service is set to System.ServiceModel.Routing.RoutingService, which is the RoutingService we need to use. Our router endpoint is set to http://localhost:8008/OrderRouter. This is the endpoint clients will send the order messages to and our router will distribute them to the OrderService according the order content.
Our router endpoint contract is the System.ServiceModel.Routing.IRequestReplyRouter. There are 4 possible routing interface defined:
We also set a behavior for the routing endpoint, which holds a <routing> node:
We set the routeOnHeadersOnly to false, since we want to route on the content of the message and not only on the message headers.
The filterTableName we set to a filtertable which contains the filters to route on
The rest of our routing configuration to set up the content based routing (click to enlarge):
There’s a few things to notice here:
- We defined a routing section, which holds a filterTable and which holds a few filters on which the routing will be based
- We defined a client section which holds the endpoints the router can send messages to
We will start with the <client> configuration. This configuration node contains 2 endpoints: a “LowOrdersEndpoint” and a “HighOrdersEndpoint”. These are the endpoints we define the router can send messages to. Our client will send the Order message to the router and the router will have to route the order message to an OrderService endpoint according to the Order Total price. It is important to remember that the endpoints the routing service can send to are defined at the <client> section.
Next on is the <routing> node. In this node we can set all the routing configuration.
We will start by defining the filters on which our routing of the order services will depend on
We defined 2 filters, named “HighOrderFilter” and “LowOrderFilter”. The HighOrderFilter will route the messages with a total price of 500 to the /OrderService/High and the other messages will be routed to the /OrderService/Low.
To set this up, we need to define our filters. First up we define the HighOrderFiler of which we set the filterType to XPath which allows us to query our message by an XPath query. The filter checks if the Total parameter in our order message is larger or equal to $500. Notice we have set the filterData to “//ns:Total >= 500”. So we use an XPath query to filter for any Total xml node in our SOAP message where the Total property value is larger than $500. The ns: is a prefix we defined at the nameSpaceTable, where we defined a prefix called ns which points to the namespace that will be used in our Order SOAP message data contract.
The namespace can be found in the WSDL of our OrderService, since I set the httpGetEnabled to true for our ServiceMetaData at the behavior of our OrderService:
If we visit the XSD at our localhost url:
You can see it’s the Order datacontract and the namespace used for the contract.
Finally we define another filter, called LowOrderFilter, which we give a filterType of MatchAll. This means if this filter gets validated, every request passed for this filter. So this means it is important that the HighOrderFilter gets validated first and if the request does not match the HighOrderFilter (meaning the order price is smaller than $500), it will validate against the LowOrderPrice which has a MatchAll, meaning that every order will pass this filter.
Only thing that is remains to set is the filterTable:
We define a filterTable called “OrderFilterTable”, which is also the filterTable which is pointed to at our <routing> behavior node. So our RoutingService knows it has to use the filtertable shown above, which has 2 filter references. Both these filters point to a defined filter, which we already defined before. They also both point to an endpointName, which we also defined before at the <client> section. The filtertable allows you to set a priority on the filter, defining the importance of the filter and which filter should be executed first.
Since our LowOrderFilter is a MatchAll filter, we need to make sure our HighOrderFilter has a higher priority, so it is executed first. Otherwise high orders would also end up at the low order service. To make sure the highOrderFilter gets validated first, we set the priority to a higher number then the LowOrderFilter. I just randomly picked 100 and 0 for the priority, but it can aswell be 3 and 2 as priority.
So our HighOrderFilter is the filter that the router validates against first and if the message validates against the filter, it will be sent to the endpoint “HighOrdersEndpoint, which is defined at our client endpoints. If it does not validate against the HighOrderFilter, it will get validated against the LowOrderFilter, which will pass for any order and it will be send over to the LowOrdersEndpoint.
So let’s host our OrderService and our RoutingService at the Services Console application:
It hosts the OrderService with 2 endpoints and the RoutingService. It also writes the endpoints to the Console application.
Now let’s create a client that can submit an order to our router. Add a reference to our WCF.Routing.Services project, so we have quick access to the IOrderService interface.
The configuration at our client console application:
Notice the address of the client endpoint is the ROUTER address, not any of the OrderService endpoints! The contract of the endpoint is the IOrderService contract, the contract of our OrderService.
Our client console application:
So we submit an order of $59.99 to our router service. Considering the price is below $500, it should end up at the /OrderService/Low endpoint.
When running our services console application, which hosts the service and the router:
When we invoke the client console application which submits a low order to our WCF ServiceRouter:
As expected, the order message was delivered at the /OrderService/Low endpoint. This means the HighOrderFilter was validated first against the order message content, but since the price was not large enough, it did not pass. Next the LowOrderFilter got validated, which is a MatchAll, so everything passes for this filter, so it gets passed on to the Low OrderService endpoint.
If we now change the total price of our order to a high stake order:
And run the services and client console application again:
As expected, the order message got routed to the /OrderService/High endpoint, which is for our high stake orders.
Of course this is a basic routing scenario, but if you play a bit with it, anything should be possible. Until three hours ago I had never attempted anything with the WCF 4.0 routing, nor did I know how it actually worked. Some effort and I believe you can create some quite nice solutions.
Any suggestions, remarks or improvements are always welcome.
If you found this information useful, make sure to support me by leaving a comment.
Cheers and have fun,