Model Driven Content Based Routing using SQL Server Modeling CTP – Part I
Author: Dana Kaufman
Reviewers: James Podgorski, Jaime Alva Bravo, Chris Sells, Matthew Snider, David Miller, Scott Roberts
Introduction
One of the major new WCF features in .NET 4 is the Routing Service which is a configurable WCF-based service that supports content-based routing and protocol bridging. The content based routing capability in .NET 4 allows for WCF to perform message filtering based on content contained in either the SOAP headers or within the message body. For instance, if a company has two different versions of the same service deployed, the client application can call into a central routing service. The routing service can then forward the message to the correct backend service based on information it extracts from the incoming message, such as version number. The routing service also supports error handling routing that can automatically resend the message to another destination endpoint in the event of an error.
Unfortunately, the default routing service is not the easiest to manage or configure. Fortunately, the recently released SQL Server Modeling CTP makes it easy to build model driven application and the .NET 4 router configuration is a good modeling candidate. As such, the RouterManager sample which is available for download from MSDN Code Gallery, shows how this can be accomplished using Visual Studio 2010 Beta 2 and the SQL Server Modeling CTP which was released in conjunction with PDC09. This is the first in a series of articles that will cover the architecture and concepts used in the RouterManager sample application to create a fully model driven content based router.
The SQL Server Modeling CTP can be used to create a domain specific language (DSL) that can make the routing configuration of the WCF routing service much simpler. This is accomplished by first defining a language that is human readable, which will provide IT Operations and others within an organization who are not expert developers with an easier way to interact with applications. The CTP’s modeling language features make it easy to define the structure of the configuration data in a human readable representation that can be shared within an application’s modules, tiers and tools or between even between applications. Also included, as part of the CTP, is the SQL Server Modeling Services, which provides functionality to store, access and share both the physical and metadata representation of the model. The Modeling CTP also provides a visual tool called “Quadrant” for viewing and interacting with models and model data.
The Application
Looking at the .NET 4 WCF Routing service, the out of the box way to configure the routing behavior is by editing the XML in the App.config file. Instances of the Routing Service have filters applied to them which are the rules used to inspect the message and determine the message destination. Below is a sample router rules configuration from as it would appear in an App.config file:
<routing>
<filters>
<!--define the different message filters-->
<!--define an xpath message filters to look for the custom header coming from the client-->
<filter name="XPathFilter" filterType="XPath" filterData="/s12:Envelope/s12:Header/version = Precise"/>
<filter name="XPathFilter1" filterType="XPath" filterData="/s12:Envelope/s12:Header/version = Rounded"/>
<!--define an endpoint name filter looking for messages that show up on the virtual regular calculator endpoint-->
<filter name="EndpointNameFilter" filterType="EndpointName" filterData=“ServiceA"/>
</filters>
<filterTables>
<filterTable name="filterTable1">
<!--add the filters to the message filter table-->
<!--first look for the custom header, and if we find it, send the message to the rounding calc endpoint-->
<add filterName="XPathFilter" endpointName="roundingCalcEndpoint" priority=“0"/>
<add filterName="XPathFilter1" endpointName=“regularCalcEndpoint" priority=“1"/>
<!--if the header wasn't there, send the message based on which virtual endpoint it arrived at-->
<!--we determine this through the endpoint name, or through the address prefix-->
<add filterName="EndpointNameFilter" endpointName="regularCalcEndpoint" priority="1"/>
<add filterName="EndpointNameFilter" endpointName="roundingCalcEndpoint" priority=“1"/>
</filterTable>
</filterTables>
</routing>
Listing 1
This configuration defines three router filters. A router filter is a rule the router uses to evaluate and take action on a message. The first filter rule instructs the router to the look for “version” element in the SOAP header with a value of “Precise”; if the router finds a match, it forwards the message to the regular calculator service via an endpoint called “regularCalcEndpoint” which is defined elsewhere in the App.config. The second filter rule indicates if the SOAP header “version” contains a value of “Rounded” then send the message to the rounding calculator service. The last router filter indicates if the message arrives on an endpoint named “ServiceA” then send it to the regular calculator service and if that service is unavailable, send it to the rounding calculator service.
The routing configuration in Listing 1 is very complex, requiring the person maintaining the configuration to understand and modify multiple sections of the XML contained in the App.config file. Creating a language specifically for the task of router configuration could make this much easier. The example listing below shows the same router configurations but implemented in a router DSL:
router begin
if header version = "Precise" priority 0 destination https://localhost:9090/regularcalc
if header version = "Rounded" destination https://localhost:9091/roundingcalc
if endpoint = "ServiceA" destination https://localhost:9090/regularcalc, https://localhost:9091/roundingcalc
end
Listing 2
As you can see, this is a much simpler syntax. The router language uses “if” statements to specify each filter. Each if statement also contains a destination section requiring only a single line for each rule instead of requiring the editing of multiple sections in the XML as in the above example.
Architecture
The RouterManager sample implements the above router language using the SQL Server Modeling CTP grammar support. The rules configuration data is modeled by using the “M” language. The CTP toolchain is used by the RouterManager to compile the DSL configuration information into Router configuration model instance data, which is stored in a Repository database hosted on SQL Server. The RouterManager runtime knows how to load router rules from the Repository database. Lastly, the “Quadrant” tool can be used to view and interact with the Router configuration data contained in the Repository. A diagram of the RouterManager sample architecture is shown below:
Diagram 1
Implementation
Imagine a company deploys a financial calculation service that is used by a number of departments. After the service is in use for a while, they realize that they can optimize the processing by rounding the results. They don’t want to disturb the existing applications that require very specific types of results; so instead they simply roll out a second service that implements the new logic. The company then deploys the .NET 4 WCF routing service to receive both the old and new incoming messages from the client applications and based the message header, routes the messages to the correct version of the backend service.
The RouterManager sample simulates the above example. The sample consists of the RouterManager executable host written in C#, which is an implementation of the WCF Routing Service that knows how to load the Router configuration from the Repository. At startup it opens up a Routing Service host that listens for incoming messages to forward to the correct remote endpoint based on its configuration. RouterManager also has a controller interface exposed as a second host endpoint that is used for remotely configuring the router.
The sample project also contains a pair of example webservices that implement a simple calculator which exposes methods for add, subtract, multiply and divide. One instance of the web service returns regular calculated results while the second instance returns rounded results.
A test client is also included that can be used to send messages with different configuration, through the router to the backend services and displays the results. For instance, it can set the SOAP header “version” to a value that the router will then inspect and send to the appropriate calculator service. The test client also contains a tab that can be used to edit router configurations and send the new instructions to the router controller. When the router controller receives new configurations, it converts the input text into configuration data which is turned into SQL and pushed into the Repository database. The controller has a second operation which is used to signal the RouterManager to reload the Routing Service router rules from the Repository database. An image of the RouterManager sample in the Visual Studio Solution Explorer is shown below:
Image 1
Running the RouterManager:
1. Load the RouterManager.sln solution in Visual Studio 2010 Beta 2.
2. Edit the redeploy_router.bat file in the RouterModel project to make sure the path to the SQL Server Modeling CTP bin directory is set properly.
3. Build the RouterManager solution.
4. Open a Command Prompt and go to RouterModel\bin\Debug directory of the solution. Execute the redeploy_router.bat file. This will use our Router grammar to parse the Router filter rules sample instance data contained in the file Router.txt and generate a Router.m file which will then compile and install the resulting image into the repository. The following schemas should then be visible in SQL Server Management Studio within the Repository database: Router.RouterRules, Router.FilterTypes, Router.FilterTypes.Destinations, and Router.DestinationList. The code name "Quadrant" modeling tool can also be used to explore the repository for these models.
Note: Ignore any M6010 or M6046 warnings. The M6010 warnings occur the first time the redeploy_router.bat file is run because the Router model and instance data has not yet been installed.
5. Run the four projects executables by pressing the “Start Debugging” button which will start all of the executables.
A screenshot of the running applications are shown below:
Image 2
By default, the RouterMananager loads up configured similar to the example routing rules specified above in Listing 2. The RouterManager can be tested using the Router Test Client. Press the “Call Services” button to send SOAP message for the Add operation to the Calculator service which includes a header named “version” with a value of “Precise”. After a second, a result of “Result = 3.579” should be displayed in the Response tab. The RouterManager forwarded the message to the regular calculator. Next select the “Add Header version = “Rounded”” radio button and press “Call Services” again. This time, the Response window will have “Result = 3.6” because the RouterManager forwarded the message to the Rounding calculator service.
Click on the “Configure Router” tab and the current set of Router rules will be displayed. Change the text in the first rule version = “Precise” to version =”Rounded” and the text in the second rule from version =”Rounded” to version = “Precise”.
The full text of the router rules should look like this:
router begin
if header version = "Rounded" priority 0 destination https://localhost:9090/servicemodelsamples/regularcalc
if header version = "Precise" destination https://localhost:9091/servicemodelsamples/roundingcalc
if endpoint = "ServiceA" destination https://localhost:9090/servicemodelsamples/regularcalc, https://localhost:9091/servicemodelsamples/roundingcalc
end
Listing 3
Press the “Configure Router” button and a new console will appear as the SQL Server Modeling CTP processes the router configuration information, generates new instance data for the model and loads the new configuration into the Repository (this will be covered in-depth in Part II).
The new router rules should be ready. Click on the “Test Service” tab to display the web service testing interface. Select the “Add Header version = “Precise”” radio button and press “Call Services” again. Then press the “Call Service” button on the Router Test Client. The Response window will display “Result = 3.6” indicating that the Rounding calculator service was called this time based on the updated routing rules.
The RouterManager sample supports five different routing filters. The table below lists the supported filters and describes their use:
Filter |
Description |
header |
Message contains a SOAP header with a specific value(supports SOAP 1.1 and 1.2 envelopes) |
endpoint |
Message arrives on a specific endpoint |
address |
Message arrives on a specific address |
addressprefix |
Message arrives on an address that contains the specified prefix |
matchall |
Any message |
To configure the RouterManager via the router language, the RouterManager exposes a controller service defined by the IRoutingController. The definition of the IRoutingController interface is shown below:
public interface IRoutingController
{
[OperationContract]
void RefreshRouterFilters();
[OperationContract]
void BuildRoutingModel(String rm);
}
Listing 4
The controller has two operations. The BuildRoutingModel() takes in a string that contains the new routing configuration and uses the SQL Server Modeling CTP to turn the configuration into Router Model instance data and insert the new configuration into the SQL Server Model Services enabled Repository database. (How this works using the SQL Server Modeling CTP tool chain will be covered in depth in Part II).
The second controller method, RefreshRouterRules(), leverages the Entity Framework to query the Repository for the latest router rules. It then builds a set of router filters based on the rules and re-programs the Routing Service with the new instructions. The .NET WCF Routing Service has a special method called ApplyConfiguration() which takes in a new set of router configurations and applies them to the Routing Service without the need to take down the routing service listener. Any messages in flight before the call to ApplyConfiguration() will be processed by the old configuration and any messages sent from new connection to the router service will be processed with the new router configuration. The implementation of RefreshRouterRules() makes use of this method to swap in the new router configuration on the fly without dropping messages.
On the test client, clicking “Configure Router” causes the client to first call the BuildRoutingModel(), passing the configuration text. When that operation complete the test client then calls the RefreshRouterRules() operation to inform the RouterManger to reload the router rules from the Repository database.
Note: The routing control interface is split into two different operations to decouple the insertion of new configuration data into the Repository from the re-loading of the rules into the Routing Service. In real world scenarios multiple instances of the RouterManager would probably be deployed. The configuration of the router to the Repository could be done in one step and then use technology such as SQL Server Query Notifications in conjunction with the RefreshRouterRules() operation. When leveraged in this way the database could signal to multiple router the to re-load their router rules.
The controller host endpoint is defined via the WCF configuration information specified in the RouterManager project’s App.config file. This offers the flexibility of allowing the Router controller host configuration to be modified to add WCF security or changed the protocol binding used by the Router controller through the App.config.
Conclusion
That concludes the overview of the RouterManager sample application. Combining the SQL Server Modeling CTP and the .NET 4 Routing Service makes any easy to use, model driven solution for content based routing of WCF messages. Part II of the “Model Driven Content Based Routing” will cover the Router model and Router language definitions as well as how they are used with the SQL Server Modeling CTP.