Implementing content based routing using the Windows Application Server (Dublin) forwarding service

Dublin’s application server incorporates a series of runtimes services that complement the runtime behavior of a WCF service host on areas such as lifecycle, persistence, message routing, etc. Among those services, the forwarding services provides high performance message routing across different services. By providing a robust foundation for messaging routing, Dublin’s forwarding service can address really complex service composition and endpoint virtualization scenarios which are traditionally very hard to implement in real world SOA applications. Capabilities such as service aggregation, content-based routing, protocol translation or data partitioning are notorious for requiring a lot of infrastructure logic in order to work properly and consequently can be drastically simplified by the use of a technology like the forwarding service.

Dublin’s forwarding service proposes a versatile model for message routing based on the concepts of WCF filters and filter tables. This post does not intend to explain the intricacies of the forwarding service; instead we are going to explore a practical example of how to leverage this new technology to implement a traditional intelligent endpoint routing scenario.

Our scenario starts with a service hosted on multiple endpoints across a server farm. Messages targeted to that service will be routed to different endpoints depending on its content. Additionally, the rules that control the content can be subject to frequent changes that need to be incorporates without impacting the different endpoints. By using Dublin’s application service, we could conceive the following topology to address that scenario.

On this scenario, the forwarding service leverages XPath message filter and filter tables to dynamically route messages to the different service’s endpoints. For the simplicity of our example let’s assume our target service implements the following contract that processes a financial transaction.

 

   1:  [ServiceContract]
   2:  public interface IService
   3:  {
   4:   
   5:      [OperationContract]
   6:      bool ProcessTrasaction(Transaction transaction);
   7:   
   8:  }
   9:   
  10:  [DataContract]
  11:  public class Transaction 
  12:  {
  13:      private string transactionId;
  14:      private double amount;
  15:      private string customerId;
  16:   
  17:      [DataMember]
  18:      public string TransactionId
  19:      {
  20:          get { return transactionId; }
  21:          set { transactionId = value; }
  22:      }
  23:   
  24:      [DataMember]
  25:      public double Amount
  26:      {
  27:          get { return amount; }
  28:          set { amount = value; }
  29:      }
  30:   
  31:      [DataMember]
  32:      public string CustomerId
  33:      {
  34:          get { return customerId; }
  35:          set { customerId = value; }
  36:      }
  37:   
  38:  }

 

 

The service implementation not really relevant to this example. In order to distribute the message traffic we have decided to host the service on multiple endpoints as illustrated in the following configuration file.

   1:  ...
   2:      <services>
   3:              <service name="Service" behaviorConfiguration="ServiceBehavior">
   4:                  <!-- Service Endpoints -->
   5:                  <endpoint address="/ep1" binding="basicHttpBinding" contract="IService">
   6:          
   7:                      <identity>
   8:                          <dns value="my server"/>
   9:                      </identity>
  10:                  </endpoint>
  11:   
  12:          <endpoint address="/ep2" binding="basicHttpBinding" contract="IService">
  13:         
  14:            <identity>
  15:              <dns value="my server"/>
  16:            </identity>
  17:          </endpoint>
  18:   
  19:          <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
  20:              </service>
  21:          </services>
  22:  ...

No need to mention that real world scenarios might require way more complex topologies :)

Now that we have our sample service hosted on multiple endpoints, it is time to enable certain level of intelligent routing between the these endpoints. In order to accomplish that, we should host an instance of the Dublin’s forwarding service and configure the appropriated routing rules. However, before we get to that point, we should enable a couple of artifacts in a global configuration file like machine.config. Specifically, we should add the forwarding behavior to the behavior extensions list so that it can be accessible by the forwarding service.

   1:  ...
   2:  <behaviorextensions>
   3:   
   4:  <add name="forwardingbehavior" 
                          type="microsoft.processserver.messaging.configuration.forwardingbehaviorextensionelement,
                          microsoft.processserver.messaging, version=3.0.0.0, culture=neutral,
                          publickeytoken=31bf3856ad364e35"/>
   5:   
   6:  <add name="marshalingbehavior" 
                          type="microsoft.processserver.messaging.configuration.marshalingextensionelement, 
                          microsoft.processserver.messaging, version=3.0.0.0, culture=neutral,
                          publickeytoken=31bf3856ad364e35"/>
   7:   
   8:  </behaviorextensions> 
   9:  ...

Additionally, we should also add the filtering configuration section which details the specific syntax for expressing routing rules.

   1:  ...
   2:  <configsections>
   3:   
   4:  <sectiongroup name="system.servicemodel" type="system.servicemodel.configuration.servicemodelsectiongroup, 
                                       system.servicemodel, version=3.0.0.0, culture=neutral, 
                                       publickeytoken=b77a5c561934e089">
   5:   
   6:  <section name="filtering" type="microsoft.processserver.messaging.configuration.filteringsection, 
                         microsoft.processserver.messaging, version=3.0.0.0, culture=neutral, 
                         publickeytoken=31bf3856ad364e35"/>
   7:   
   8:  </sectiongroup>
   9:   
  10:  </configsections>
  11:   
  12:  ...

At this point we are ready to configure a new forwarding service. we can do that by simply hosting the ForwardingService service as illustrated in the following .svc file.

Now its time for to configuring the routing rules for the different service’s endpoints. When we say configure we literally mean it :). Implementing the routing rules is purely a configuration exercise that leverages the XPath message filter and filter tables among other artifacts. For our example, we will configure a couple of basics rules that determines if a transaction is over $50000.

   1:  ...
   2:  <system.serviceModel>
   3:          <services>
   4:              <service behaviorConfiguration="forwardingConfig" 
                            name="Microsoft.ProcessServer.Messaging.ForwardingService">

5: <endpoint address="RequestReply" binding="basicHttpBinding" name="reqReplyEndpoint"

contract="Microsoft.ProcessServer.Messaging.IRequestReplyDatagram"/>

   6:              </service>
   7:          </services>
   8:          <bindings>
   9:              <basicHttpBinding>
  10:                  <binding name="BasicHttpBinding_IService">
  11:                      
  12:                      <security mode="None">
  13:                          <transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
  14:                          <message clientCredentialType="UserName" algorithmSuite="Default"/>
  15:                      </security>
  16:                  </binding>
  17:              </basicHttpBinding>
  18:          </bindings>
  19:          <behaviors>
  20:        
  21:              <serviceBehaviors>
  22:                  <behavior name="forwardingConfig">
  23:                      <forwardingBehavior filterTableName="contentFilterTable" filterOnHeadersOnly="false"/>
  24:            
  25:            <serviceMetadata httpGetEnabled="true"/>
  26:                  </behavior>
  27:                  <behavior name="ServiceBehavior">
  28:                    
  29:                      <serviceMetadata httpGetEnabled="true"/>
  30:                    
  31:                      <serviceDebug includeExceptionDetailInFaults="false"/>
  32:                  </behavior>
  33:              </serviceBehaviors>
  34:          </behaviors>
  35:          <client>
  36:              <endpoint address="http://localhost:49159/SampleService/Service.svc/ep1" binding="basicHttpBinding" 
                             bindingConfiguration="BasicHttpBinding_IService" 
                             contract="*" name="BasicHttpBinding_IService1"/>

37: <endpoint address="http://localhost:49159/SampleService/Service.svc/ep2" binding="basicHttpBinding"

bindingConfiguration="BasicHttpBinding_IService"

contract="*" name="BasicHttpBinding_IService2"/>

  38:          </client>
  39:          <!--FILTERING SECTION-->
  40:          <filtering>
  41:              <filters>
  42:                  <filter name="transactionFilter1" filterType="XPath" 
                               filterData="boolean(//*[local-name()= 'Amount']/text() &gt; 50000)"/>
  43:                  <filter name="transactionFilter2" filterType="XPath" 
                               filterData="boolean(//*[local-name()= 'Amount']/text() &lt; 50000)"/>
  44:              </filters>
  45:              <filterTables>
  46:                  <table name="contentFilterTable">
  47:                      <filters>
  48:                          <add filterName="transactionFilter1" mappedValue="BasicHttpBinding_IService1"/>
  49:                          <add filterName="transactionFilter2" mappedValue="BasicHttpBinding_IService2"/>
  50:                      </filters>
  51:                  </table>
  52:              </filterTables>
  53:          </filtering>
  54:      </system.serviceModel>
  55:  ...

Notice that the filter table "contentFilterTable" in line 46 associates specific filters (lines 42-43) with client's (lines 36-37) configured to forward the message to a service endpoint. In our case, we have a couple of mutually exclusive rules which determine that one endpoint will process the transactions larger than $50000 while the other will process the rest of the transactions.

With this simple configuration we leverage Dublin’s forwarding service to distribute messages across different service’s endpoints based on XPath Boolean expressions. It is important to notice that the forwarding service can leverage any WCF message filters such as Address, Endpoint, XPath, etc. Additionally, developers can create their own message filter and integrate it with the forwarding service.

.

2 Comments

Comments have been disabled for this content.