Friends don't let friends do point to point. Anyone who has designed services to solve a business problem or built a distributed composite application using service-oriented techniques has, consciously or not, discovered the hazards of client applications knowing too much about all of the services with which they interact.
There are two main reasons for this. First, the more a client knows about a service, the less flexibility the client and the service have to change and evolve. And since one of the principal reasons for distributing components as services in the first place is reuse, factoring service into cohesive functions and defining explicit boundaries between how the client interacts with, or consumes the service is key. Second, the cost to integrate an application increases in proportion to the number of endpoints (interfaces) required to deliver the desired business value. This cost is manifested not in only establishing each initial interface, but also in managing change across the interfaces. These two factors create significant friction because they encourage application designs that exhibit low coupling, but at the same time the value of an application that isn't connected to anything quickly approximates zero.
Some common problem domains in messaging scenarios that today's composite application developer must reason about include:
- Factoring the business domain into the right number of services promoting cohesion and reuse.
- Whether orchestration and workflow should take place on the client or the service (i.e., who manages the composition of interfaces)?
- Achieving loose coupling between the client and the service so that each can evolve independently.
- Managing different versions of the service in a manner that is transparent to the client application.
- Handling differences in compatible protocols between the client application and service(s).
- Decoupling the logic that determines which other parties/endpoints may be interested in an event originating from a client application.
- Translating the message representation from a client application to one or more services while the message is in flight.
In the May/June 2010 issue of CODE Magazine, I introduced many of the new features in WCF 4 that help make developers more productive including a simplified configuration experience and the support for WS-Discovery, both of which help developers focus on business value rather than plumbing. These features are undeniably valuable regardless of the messaging patterns you employ.
In this article, I'm going to build on the productivity improvements in WCF 4 by spending some time looking at some common integration scenarios facing those who build distributed composite applications and how common integration patterns help address these scenarios. I'll then provide an introduction to the Routing Service in WCF 4 and dedicate the rest of the article to showing you how you can address common messaging scenarios by implementing discrete (or combining) integration patterns to address these scenarios quickly and easily using the Routing Service in WCF 4.
Note that design patterns (and even more specifically integration design patterns) are a big topic and very well documented in various bodies of work (see sidebar Favorite Books on Patterns). As such, my goal is not to provide a treatise on design patterns or integration design patterns. Instead, in the true spirit of pattern language, I will take an implementation technology agnostic scenario-driven approach to talk about some of the most common scenarios you are likely to come across that will force you to consider breaking away from the point-to-point integration techniques with which you already may be comfortable. I'll focus on the scenarios I encounter and corresponding patterns I regularly apply in my own work helping clients, along with rationale for why each pattern might be helpful within each scenario.
With these foundations in place, I'll shift focus to how these patterns can be implemented with the new Routing Service and then I'll tie many of these patterns together in a practical business scenario which will reflect how patterns often come together as interdependent pattern clusters to address common messaging problems head on.
A Quick Word on Patterns
What Is a Pattern?
Whether you realize it or not, patterns are everywhere. Consciously or subconsciously, we use patterns every day to solve common programming problems. When we don't realize that we are using a pattern, we may be falsely led to believe that we are doing something stupendous and unique. However, when we are approached by the same or similar problem in our next work, chances are we will reach for the same approach we found was helpful in solving the “original” problem in our previous work.
This is precisely the idea behind design patterns. Put simply, a design pattern is a way to formally document a solution to a design problem so that others might benefit from its application. These solutions are then organized into pattern clusters that form a pattern language. To quote Christopher Alexander, renowned architect (who led the architecture and design for more than 200 buildings across the world) and the father of pattern language: “The elements of this language are entities called patterns. Each pattern describes a problem that occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice.”
In software engineering, we often find ourselves faced with similar problems over and over again, and design patterns offer a way forward in addressing these problems in a manner that is not only proven, but provides the designer with the necessary lubrication to revisit the solution and apply successively more sophisticated extensions of the pattern as a system matures and becomes more complex in response to change. Careful study and practical experience applying design patterns leads to emergent architecture resulting in systems that are responsive to and embrace change. This means that if we stop to consider how other smart people have solved a particular design challenge that may seem incredibly unique, we will not only deliver value to our customers faster but significantly increase the shelf life of the applications that we build!
Many bodies of work document design patterns in various problem domains and texts such as “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides. In addition, “Patterns of Enterprise Application Architecture” by Martin Fowler belongs on the shelf of any developer or architect who wants to be equipped with a toolbox for solving recurring problems in software design.
In the domain of integration patterns, Gregor Hohpe and Bobby Woolf authored a highly influential book on integration patterns called "Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions." Throughout this article, I will refer to various patterns from this work, as well as a pragmatic collection of generalized patterns contributed by notable authorities on patterns (including Gregor Hohpe) organized by the Microsoft Platform Architecture Guidance Group (PAG) into a single work called “Enterprise Solution Patterns” but will leave more in depth study of these patterns as an exercise for you.
Finally, it is important to understand that patterns generally fall into three categories:
- Architectural
- Design
- Implementation
Architectural and design patterns should, by their very definition, be platform and product agnostic, such that anyone can reason about the fitness of a pattern without regard to a specific vendor technology. Let's take a moment to define each.
In his 1996 work, Pattern-Oriented Software Architecture, Frank Buschmann defines an architectural pattern as one which “...expresses a fundamental structural organization schema for software systems. It provides a set of predefined subsystems, specifies their responsibilities, and includes rules and guidelines for organizing the relationships between them.”
Erich Gamma, contributing author to Design Patterns: Elements of Reusable Object-Oriented Software cited previously, defines a design pattern as follows: “A design pattern provides a scheme for refining the subsystems or components of a software system, or the relationships between them. It describes a commonly recurring structure of communicating components that solves a general design problem within a particular context.”
Lastly, the PAG defines an implementation pattern as a “low-level pattern specific to a particular platform. An implementation pattern describes how to implement particular aspects of components or the relationships between them, using the features of a given platform”.
For this article, I am going to presuppose that we have decided on a distributed application or service-oriented architecture pattern. I'll take a platform agnostic approach that focuses on scenarios and forces that might lead to the selection of a specific pattern or group of design patterns, and will provide you with very specific implementation patterns or recipes for realizing these design patterns with the WCF Routing Service.
Now, I want to elevate this focus to a conceptual level to help you understand why re-thinking point-to-point integration might be a good idea. As I progress, I'll combine explicit pattern language to show you how you might accomplish the goal of a scenario and will finally walk you through a practical implementation example using the Routing Service.
Common Messaging Scenarios
Beyond Point-to-Point: Virtual Endpoint/Message Broker
The key to unlocking the possibilities that enable many of the scenarios that I am going to cover next is to provide location transparency to an application. Location transparency, by its very definition, precludes point-to-point integration because the application is explicitly decoupled from knowing anything about the downstream services that will perform work on its behalf.
Why: I want the flexibility to change my service without affecting its clients.
How: Use the Message Broker (Hohpe, Woolf) pattern to expose a virtual endpoint that client applications consume instead of the actual service endpoint(s) and route incoming requests from the application to the appropriate service through an intermediary using the Message Filter (Hohpe, Woolf) pattern as depicted in Figure 1.
Note: Before jumping into the Routing Service examples, I am assuming that you have a foundational understanding of WCF. In addition, the Routing Service in WCF 4 is already well documented on MSDN (see the sidebar: Routing Service Documentation) so I will not attempt to document it here.
What: The Routing Service in WCF 4 provides an intermediary layer of messaging middleware that allows you to expose a service endpoint to a client and pass all messages from the client to an ultimate receiver configured as a client endpoint.
The Routing Service ships in a new assembly in WCF 4 called System.ServiceModel.Routing.dll.
As a WCF service, the Routing Service is instantly familiar to those already using WCF and it can easily be hosted in IIS/WAS/Server AppFabric by creating a .svc file and mapping the Service attribute to System.ServiceModel.Routing.RoutingService
as shown below:
<%@ ServiceHost Language="C#" Debug="true" Service="System.ServiceModel.Routing.RoutingService, System.ServiceModel.Routing, version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
In the example code provided with each scenario, I've named my .svc file RoutingService.svc
and will refer to it as such in the rest of the examples. Figure 2 shows the Routing Service deployed to IIS/WAS as an application and Figure 3 shows additional details provided by Windows Server AppFabric (although Windows Server AppFabric is not required to take advantage of the Routing Service). As with any WCF service, any CLR host is capable of hosting the Routing Service, but IIS/WAS/Server AppFabric provide many benefits that you'll want to take advantage of if you can.
Message Broker in Action
Open the RoutingService.sln
solution provided with this article. You will notice three projects as shown in Figure 4. In addition to the Routing Service project, there is a Console Application project called “Client” which will act as our client/test harness and a project “OneWayService” which is a WCF Service Application which we'll use throughout this article.
Note that there is nothing special about any of these projects. The OneWayService
is a very simple one-way WCF service that contains common boilerplate service configuration as shown in Listing 1.
Listing 1: OneWayService sample service configuration
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="OneWayService.OneWayService">
<endpoint name="OneWayBasic"
address="http://localhost/OneWayService1"
binding="basicHttpBinding"
contract="OneWayService.IOneWayService"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost/OneWayService1"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
If you look closer at the Client project's configuration (in the App.config
file), you will notice that it is pointing to RoutingService
instead of the OneWayService
as shown in the endpoint element below:
<endpoint address="http://localhost/RoutingService/RoutingService.svc/MessageBroker"
binding="basicHttpBinding"
bindingConfiguration="OneWayBasic"
contract="ServiceReference1.IOneWayService"
name="OneWayBasic" />
With the RoutingService
deployed to IIS, start the OneWayService
in Visual Studio. You should see the dialog shown in Figure 5 indicating that the service is ready. Minimize the Console window, but be sure to keep the service running.
Now, start the client by right-clicking on the Client project, select Debug and Step into new instance. Hit F11
until you get to line 16 inside the Main
method of the Console Application (Figure 6). Notice that I've pinned a DataTip
reminding me that I am about to connect to the Routing Service as opposed to a point-to-point connection to the OneWayService.
Hit F5
, and when the client will indicate that it has sent the message. Now bring up the Console window for the OneWayService.
As shown in Figure 7, the OneWayService
has indeed received the message.
If you open the Web.config
file in the RoutingService
WCF Application project, you'll find that on the surface it looks like any old WCF service configuration. As you can see in Listing 2, we have an address which uniquely exposes the service endpoint, a binding which in this case provides compatibility with the WS-I Basic Profile 1.0 specification, and a contract of type System.ServiceModel.Routing.ISimplexDatagramRouter
, which is one of four contracts supported by the Routing Service.
Listing 2: Routing Service Message Broker recipe
<system.serviceModel>
<services>
<service name="System.ServiceModel.Routing.RoutingService">
<endpoint name="BasicHttp"
address="MessageBroker"
binding="basicHttpBinding"
bindingConfiguration=""
contract="System.ServiceModel.Routing.ISimplexDatagramRouter" />
</service>
</services>
<client>
<endpoint address="http://localhost/OneWayService1"
binding="basicHttpBinding"
contract="*"
name="OneWayService1" />
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<routing filterTableName="RoutingTable"/>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<routing>
<filters>
<filter name="MessageBroker" filterType="MatchAll" />
</filters>
<filterTables>
<filterTable name="RoutingTable">
<add filterName="MessageBroker" endpointName="OneWayService1"/>
</filterTable>
</filterTables>
</routing>
</system.serviceModel>
You can choose the appropriate contract based on what you are trying to achieve, and full documentation is available on MSDN (see the sidebar Routing Service Documentation). The ISimplexDatagramRouter
contract provides support for routing one-way datagram messages, as well as asynchronous invocation and the use of session, should a client choose to use them. The only requirement is that the channel shape of the client and service match, which is really nothing surprising since binding equivalence on both ends of the wire has always been a requirement.
Inspecting the configuration in Listing 2 a bit further, you can see that the Routing Service is configured with a single client endpoint which points to the OneWayService
that you just indirectly communicated with through the Routing Service.
The key to realizing this scenario is the application of the Message Broker (Hohpe, Woolf) and Message Filter (Hohpe, Woolf) patterns.
Let's take a closer look under the hood on Routing Service to see how I implemented these design patterns with nothing but declarative WCF configuration.
First, I configured the Routing Service to expose a virtual endpoint to the client while routing the message to the OneWayService
by enabling routing as a service behavior and providing a filter table name:
<routing filterTableName="RoutingTable"/>
A filter table is a collection of table entries (instances of FilterTableEntryElement
types) which each include a unique name to identify the filter (FilterName
), an endpoint Uri (EndpointName
) and some additional properties/elements which we will cover as we progress. You can and often will add several entries to the filter table to accomplish the desired result.
Next, to meet the goal of this scenario, I added a single table entry and set the filterName
attribute to MessageBroker
and endpointName
to the name of the client endpoint which points to the ultimate receiver: OneWayService
:
<add filterName="MessageBroker" endpointName="OneWayService1" />
The key to tying the incoming messages to the filter entries is configuring one or more message filters to filter out unnecessary messages and map messages that match the filter to the filter table entry so that the messages can be routed successfully.
The MessageFilter
class supports a number of filter types, which are responsible for applying the filtering logic. For this scenario, the filter of interest is the MatchAll
filter, which, as its name implies, is not at all picky about providing messages with a first class, one way ticket to whatever service matches the filter table entry:
<filters>
<filter name="MessageBroker" filterType="MatchAll" />
</filters>
The fact that this provides location transparency to the client through an intermediary is alone a very powerful thing indeed because you now have a tremendous amount of flexibility, including the ability to change the address of the OneWayService
(which can often happen when the service is moved to another server), the ability to apply rules to determine which endpoint the message should go to (helpful in versioning or to prioritize routing), the ability to send a copy of a message to multiple services, or to bridge one protocol from a client (say basicHttpBinding) to another (say netTcpBinding). These are only a few examples that we'll explore in this article, but the point is that once you break away from point-to-point messaging, the possibilities are endless.
High Availability
Availability has long been a core pillar of enterprise applications, and the emergence of cloud computing will serve to democratize highly available applications as a commodity for the masses. This is going to push on-premise hosting and so-called “private cloud” environments to provide capabilities on-premise that complement the elasticity of scale inherent in the cloud.
Both cloud and on-premise solutions go a long way in providing increased availability, reliability and scale by applying patterns such as Load Balanced Cluster (PAG) and Failover Cluster (PAG). According to Enterprise Solution Patterns (a collection of patterns and guidance documented by Platform Architecture Guidance Group) “[t]he Load-Balanced Cluster pattern addresses how to maintain acceptable performance through the design and implementation of a scalable infrastructure tier. This pattern describes the common ways to balance incoming Internet Protocol (IP) traffic across a set, or farm, of read-only or application servers.” The PAG goes on to define the Failover Cluster pattern as a pattern that “helps you design a highly available application infrastructure tier that protects against loss of service due to the failure of a single server or the software that it hosts. In a failover cluster, if one of the servers becomes unavailable, another server takes over and continues to provide the service to the end-user, a process known as failover. When failover occurs, users continue to use the application and are unaware that a different server is providing it.”
One of the key benefits of service-orientation is re-use and flexibility, and if you consider the tenets of SOA in your service design, you stand to benefit from explicit boundaries (endpoints) and autonomy (self-contained units of reuse). Microsoft has taken a major step to better deliver on availability and reliability of WCF, WF and ASP.NET applications by releasing Windows Server AppFabric, which provides a set of extensions to IIS/WAS. These extensions include new service-centric management functions such as monitoring, tracking and persistence, as well as distributed caching that is capable of scaling across multiple nodes thus introducing redundancy and high availability of cached data.
Imagine a scenario where you have a billing service that is responsible for charging customers for a good or service. This service is critical as it is tied to financials, so it is important that it be both reliable and highly available.
Why: You want to ensure that service to a client application is never interrupted, even in the case of a failure in a primary or secondary service.
How: Apply the Failover Cluster (PAG) pattern on top of the Message Broker (Hohpe, Woolf) pattern to virtualize the service endpoints and provide redundancy ensuring that the service is highly available and that service failures are transparent to the client application as depicted in Figure 8.
What: While you could certainly push the service into the cloud, or leverage load balancing to increase the availability of the service as previously discussed, another option is to configure the Routing Service such that it attempts to send a message to a service and the service is unavailable, it will attempt to send the message to a second instance of the service running on a different server or fault domain.
This is precisely what back up lists allow you to do with the Routing Service. The easiest way to demonstrate this feature is to start with a Message Broker and expand it to include this functionality.
Implementing a Failover Cluster is as simple as modifying existing configuration to provide for a list of back up services that should be invoked in the event of a failure.
In Visual Studio, rename the Web.config
file back to Web.config.MessageBroker.
Rename the Web.config.HighAvailabilty
file to Web.config
.
I've taken the Message Broker configuration, the basic recipe for expanding into other scenarios, and added three additional client endpoints as shown in Listing 3.
Listing 3: Routing Service High Availability recipe
<system.serviceModel>
<services>
<service name="System.ServiceModel.Routing.RoutingService">
<endpoint name="MessageBroker"
address="MessageBroker"
binding="basicHttpBinding"
bindingConfiguration=""
contract="System.ServiceModel.Routing.ISimplexDatagramRouter" />
</service>
</services>
<client>
<endpoint address="http://localhost/OneWayService1"
binding="basicHttpBinding"
contract="*"
name="OneWayService1" />
<endpoint address="http://localhost/OneWayService2"
binding="basicHttpBinding"
contract="*"
name="OneWayService2" />
<endpoint address="http://localhost/OneWayService3"
binding="basicHttpBinding"
contract="*"
name="OneWayService3" />
<endpoint address="http://localhost/OneWayService4"
binding="basicHttpBinding"
contract="*"
name="OneWayService4" />
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<routing filterTableName="RoutingTable" routeOnHeadersOnly="False"/>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<routing>
<filters>
<filter name="OneWayService1Filter" filterType="MatchAll" />
</filters>
<filterTables>
<filterTable name="RoutingTable">
<add filterName="OneWayService1Filter" endpointName="OneWayService1" backupList="BackUps" />
</filterTable>
</filterTables>
<backupLists>
<backupList name="BackUps">
<add endpointName="OneWayService2"/>
<add endpointName="OneWayService3"/>
<add endpointName="OneWayService4"/>
</backupList>
</backupLists>
</routing>
</system.serviceModel>
In addition, I've added a new attribute, backupList, to my entry in the filter table:
<add filterName="OneWayService1Filter" endpointName="OneWayService1" backupList="BackUpServices" />
The BackUpServices
back up list is defined in the BackUpLists
element, which supports the definition of multiple back up client endpoints as shown below:
<backupLists>
<backupList name="BackUps">
<add endpointName="OneWayService2"/>
<add endpointName="OneWayService3"/>
<add endpointName="OneWayService4"/>
</backupList>
</backupLists>
The back up list indicates to the Routing Service that if the primary endpoint, OneWayService1
is not available (i.e., it fails to respond), the Routing Service should try each subsequent endpoint beginning with OneWayService2
and ending with OneWayService4
until a service responds.
High Availability in Action
Open the OneWayServices
folder in Windows Explorer and run the StartAll.cmd
file. You should see four console windows appear, each reporting that each instance of the OneWayService
is listening on a unique endpoint address. Minimize, but do not close each window.
Run the Client application within Visual Studio. Once the message is sent, verify that OneWayService1
received the message (the console will indicate that a message was received.
Now, close the console window that is running OneWayService1
and run the Client application again within Visual Studio (you may need to stop debugging from the previous session). The message is automatically routed to the first client endpoint specified in the back up list and the client is completely unaffected by the fact that OneWayService1
crashed. Repeat this process, killing each service and running the client until you run out of back up services. You will see that the Routing Service will route the message to the next available service.
Mediation
As the business value that a service provides increases, so too do the number of potential consumers or client applications. It's the simple law of supply and demand. Recalling that a key goal of service-orientation is reuse, interoperability between the client and the service is a key priority.
Sometimes, forces at play may influence decisions that adversely impact the interoperability of a service. For example, suppose that when implementing an inventory look up service, the decision was made to use a TCP/IP message channel and binary encoding to ensure that lookups are as fast and efficient on the wire as possible. Moreover, because the inventory service initially may have only been intended for use within the corporate intranet, proprietary binary over TCP/IP was probably a good choice.
However, as time goes by and the company grows, a new trading partner is introduced which is a Java shop and requires the ability to look up inventory over the Internet. The new partner is well versed in SOA and has their own Java-based SOA stack. Two immediate problems make the current inventory service ill-suited for integration. First, the inventory service is exposed using TCP/IP and a binary encoding that is not compatible with the Java stack. Second, even if you are lucky enough to have chosen to implement the inventory service in WCF, adding a second binding such as BasicHttpBinding is not a suitable solution because it would require that you expose your inventory service directly to the Internet.
In this situation, you need to increase the reach of your inventory service by making it available to HTTP/SOAP consumers, but you don't want to expose the service on the DMZ.
Why: You want to make a service available to heterogeneous clients with different messaging channels.
How: Apply the Messaging Bridge (Hohpe, Woolf) pattern to mediate the protocol between the client and the service.
What: As I mentioned earlier, simply adding a second endpoint to the inventory service is an inadequate solution because it would require the inventory service to be hosted on the DMZ which would increase the service's surface area.
Instead, you can deploy the Routing Service to the DMZ as a fa�ade to filter all messages from the Internet and mediate protocols from TCP/IP to HTTP:
In Visual Studio, rename the Web.config
back to Web.config.HighAvailability
and find the configuration file called Web.config.Mediation
. Rename this file to Web.config
and open it. You will notice a very simple configuration including a service endpoint configured with the BasicHttpBinding
which is exposed as a virtual endpoint:
<endpoint name="MessageBroker"
address="MessageBroker"
binding="basicHttpBinding"
bindingConfiguration=""
contract="System.ServiceModel.Routing.ISimplexDatagramRouter" />
Notice that I've also specified a client endpoint, which is used to route to the OneWayService
endpoint to a NetTcpBinding
version of the OneWayService:
<client>
<endpoint address="net.tcp://localhost:311/OneWayService1"
binding="netTcpBinding"
contract="*"
name="OneWayService1" />
</client>
Open the App.config
for the OneWayService
in Visual Studio and change the address and binding to the following and hit save:
<endpoint name="OneWayBasic"
address="net.tcp://localhost:311/OneWayService1"
binding="netTcpBinding"
contract="OneWayService.IOneWayService"/>
Going back to the Routing Service configuration, notice a simple filter using the MatchAll
filter type:
<filter name="HttpToTcpMediation" filterType="MatchAll" />
A simple entry in the filter table correlates the filter match to the client endpoint which is exposed as a NetTcpBinding
:
<add filterName="HttpToTcpMediation" endpointName="OneWayService1" />
Mediation in Action
Start the OneWayService
from within Visual Studio. The service should start and the endpoint address Uri should be written to the console as in previous examples. Notice that the service is being hosted on TCP port 311. Minimize (but do not close) the console window for now.
Now, in the Program class in the Client project, modify line 16 to include the binding scheme (protocol) used by the client in the message to the Routing Service that will ultimately be mediated and routed to the OneWayService:
client.OneWayMessage(String.Format("Sent message over {0}",client.Endpoint.Binding.Scheme));
Run the client, and as soon as the message is sent, bring the console window up and notice that the Routing Service has received the message on an HTTP endpoint, applied a filter rule to mediate and route the message from HTTP to TCP and send the message to the OneWayService
listening on port 311 as shown in Figure 9.
Routing on Context
By this point, I've done my best to convince you that breaking away from point-to-point messaging is the key to unlocking endless possibilities for messaging scenarios, so in the scenario and pattern discussions that follow, I will assume that you are starting with a Message Broker (Hohpe, Woolf) and Message Filter (Hohpe, Wolf) pattern which as you've read, are easily applied with the WCF Routing Service new in WCF 4.
While the ability to pass all messages through an intermediary to a different endpoint forms the foundation for new possibilities, it isn't very useful by itself.
Let's say that you've partitioned your clients such that premium customers use one endpoint and regular customers use another endpoint. Clients that send messages to the premium endpoint must be routed to a service which provides expedited shipping, while all others are routed to the default endpoint which ships all orders via regular postal ground.
Why: I want to partition messages so that certain messages go to one service while others go to a another service without regard to the message contents.
How: Use the Context-based Router (Pollack), a specialized Content-based Router (Hohpe, Woolf) to make routing decisions based on the channel on which messages arrive.
What: Building upon the Message Broker Routing Service configuration, you can add a second service endpoint to the configuration which is intended for premium customers. You can then configure a second client endpoint that points to the expedited shipping service, along with a new filter table entry that contains the name of the premium client endpoint and configure a filter type which will match all messages that arrive on the premium service endpoint with the appropriate table entry.
In the RoutingService.sln
Solution, rename the Web.config
file to Web.config.MessageBroker
. Now, find a file called Web.config.ContentBasedRouting
(this configuration file contains the recipe for implementing the Context-based Routing pattern) and rename to Web.config
. After opening the Web.config,
compare it to Listing 4 to make sure you have the right configuration file.
Listing 4: Routing Service Context-based Router recipe
<system.serviceModel>
<services>
<service name="System.ServiceModel.Routing.RoutingService">
<endpoint name="MessageBroker"
address="MessageBroker"
binding="basicHttpBinding"
bindingConfiguration=""
contract="System.ServiceModel.Routing.ISimplexDatagramRouter" />
<endpoint name="Premium"
address="Premium"
binding="basicHttpBinding"
bindingConfiguration=""
contract="System.ServiceModel.Routing.ISimplexDatagramRouter" />
</service>
</services>
<client>
<endpoint address="http://localhost/OneWayService1"
binding="basicHttpBinding"
contract="*"
name="OneWayService1" />
<endpoint address="http://localhost/OneWayService2"
binding="basicHttpBinding"
contract="*"
name="OneWayService2" />
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<routing filterTableName="RoutingTable"/>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<routing>
<filters>
<filter name="MessageBroker" filterType="EndpointName" filterData="MessageBroker" />
<filter name="PremiumEndpoint" filterType="EndpointName" filterData="Premium" />
</filters>
<filterTables>
<filterTable name="RoutingTable">
<add filterName="MessageBroker" endpointName="OneWayService1" />
<add filterName="PremiumEndpoint" endpointName="OneWayService2" />
</filterTable>
</filterTables>
</routing>
</system.serviceModel>
I've made four changes to the Routing Service configuration to support this scenario:
- First, I added a second service endpoint called Premium (see Listing 4).
- Next, I added a second client endpoint called OneWayService2.
- Then I changed the previous filter and added a new filter to each use the
EndpointName
filter type and set thefilterData
attribute to the name of the service endpoint that I want to partition messages to:
<filter name="MessageBroker"
filterType="EndpointName"
filterData="MessageBroker" />
<filter name="PremiumEndpoint"
filterType="EndpointName"
filterData="Premium" />
- Lastly, I matched the filter names above to the respective endpoint name to achieve the context-based partitioning:
<add filterName="MessageBroker" endpointName="OneWayService1" />
<add filterName="PremiumEndpoint" endpointName="OneWayService2" />
Context-based Routing in Action
With the configuration in place, right-click the OrderService
project and select “Open Folder in Windows Explorer.” In Windows Explorer, go up one level and you will find a folder called OneWayServices
(the plural folder name here is key). Find a file called StartTwo.cmd
and double-click to run it. It will start two instances of OneWayService
: OneWayService1
and OneWayService2
. At this point you should have both services up and running in console windows waiting for messages. Minimize, but do not close the console windows.
Next, back in Visual Studio, run the Client project. If all goes well, as before, you should see the client happily report that the message has been sent.
Bring the OneWayService1
window up to verify that it has indeed received the message. Now, stop the debugger in Visual Studio, and change the client endpoint address in the Client Web.config from http://localhost/RoutingService/RoutingService.svc/MessageBroker
to http://localhost/RoutingService/RoutingService.svc/Premium
as shown below:
<endpoint address="http://localhost/RoutingService/RoutingService.svc/Premium"
binding="basicHttpBinding"
bindingConfiguration="OneWayBasic"
contract="ServiceReference1.IOneWayService"
name="OneWayBasic" />
Run the client, wait for it to report that is has sent the message to the router, and open the window for OneWayService2
. You should see that it has, in fact, received the message, but OneWayService1
still has only received the first message.
Other usages for content-based routing in the Routing Service include determining the incoming address Uri. The applicability for this is similar to the example in this scenario; however, you may want to base routing decisions on the address instead of the endpoint name. To do so, you would use the EndpointAddress
or, better yet, the EndpointAddressPrefix,
which would allow you to route all messages that are targeting a given host name or DNS name.
Routing on the Contents of a Message
Often it is useful to inspect the contents of a message in order to make a routing decision. Some examples may be inspecting the SOAP action of an incoming message, or some value inside the message payload such as an element, attribute or header value. Using this content, you can make routing decisions such as prioritizing orders with a total of more than $1000 to go to an expedited shipping service, or apply some rules that determine the version of the intended service.
Why: I want to determine the destination of a message based on a value within the message itself.
How: Use the Content-based Router (Hohpe, Woolf) pattern to make routing decisions based on the contents of a message.
What: By this point you should be well adjusted to Routing Service configuration, so I'll jump right into it. In the RoutingService.sln
Solution, rename the Web.config
back to Web.config.ContextBasedRouting
. Now, find a file called Web.config.ContentBasedRouting
and rename it to Web.config
. After opening the Web.config,
compare it to Listing 5. (Note that with the fundamentals in place, I am only including the differences in order to conserve space. See Listings 2 and 3 for examples of full Routing Service configuration.)
Listing 5: Routing Service Content-based Routing recipe
<system.serviceModel>
<services>
<service name="System.ServiceModel.Routing.RoutingService">
<endpoint name="MessageBroker"
address="MessageBroker"
binding="basicHttpBinding"
bindingConfiguration=""
contract="System.ServiceModel.Routing.ISimplexDatagramRouter" />
<endpoint name="Premium"
address="Premium"
binding="basicHttpBinding"
bindingConfiguration=""
contract="System.ServiceModel.Routing.ISimplexDatagramRouter" />
</service>
</services>
<client>
<endpoint address="http://localhost/OneWayService1"
binding="basicHttpBinding"
contract="*"
name="OneWayService1" />
<endpoint address="http://localhost/OneWayService2"
binding="basicHttpBinding"
contract="*"
name="OneWayService2" />
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<routing filterTableName="RoutingTable" routeOnHeadersOnly="False"/>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<routing>
<namespaceTable>
<add prefix="temp" namespace="http://tempuri.org/"/>
</namespaceTable>
<filters>
<filter name="PremiumEndpoint" filterType="XPath" filterData="//temp:message='rush'" />
</filters>
<filterTables>
<filterTable name="RoutingTable">
<add filterName="PremiumEndpoint" endpointName="OneWayService2" />
</filterTable>
</filterTables>
</routing>
</system.serviceModel>
The idea here is that if the message parameter/element contains the value of “rush”, the message will be routed to the OneWayService2
service.
I've made the following changes to support the ability to route messages based on the contents of the message payload:
- It is necessary to add the
routeOnHeadersOnly="False"
attribute to the Routing Service behavior. This is a security feature to prevent tampering with the message body. - I've removed all filters except the PremiumEndpoint filter, changed the filter type to “XPath” and provided an XPath query in the
filterData
attribute:
<filter name="PremiumEndpoint" filterType="XPath" filterData="//temp:message='rush'" />
- I added a Namespace table which is required to map namespace prefixes to entries in the filter table:
<namespaceTable>
<add prefix="temp" namespace="http://tempuri.org/"/>
</namespaceTable>
Content-based Routing in Action
To run this sample code, simply open the Client project, go to the Program.cs
file and in the Main
method where I am calling the service via the proxy, change the message parameter from “Hello Routing Service” to “rush”:
client.OneWayMessage("rush");
Ensure OneWayService1
is running (start it in Visual Studio) and run the client. You should see that the message is delivered to OneWayService1
. Now, change the message parameter back to “Hello Routing Service” or anything else you'd like, run the client and notice that no exception is thrown, but the message isn't delivered to the service. This illustrates that the content-based routing filter is working as well as the subtle benefit of exception shielding from the client.
By applying the Content-based Router pattern on top of the Message Broker pattern, you can implement routing based on priority, versioning, or anything else you'd like. Pretty powerful isn't it?
Sending a Message to Multiple Services
There are several scenarios where the need for more than one recipient to receive the message is necessary.
You may need to send a notification message (in response to an event) to several identical services that are perhaps deployed in a geographically distributed manner so that they can do some logging or other useful work. In fact, one-way multicast notifications are often an inner pattern to implementing pull-based publish subscribe scenarios in which a service (in this case a router) may notify one or more services that an event has taken place, and the downstream service will then request a message to synchronize content or some other information of interest.
Sometimes you want to distribute a message of interest to more than one service based on some kind of routing logic. For example, when an order arrives at a router, you may want to send a copy of the order message to the inventory service, the billing service and the shipping service. These services need not know anything about the point of sale application, and the client application can be completely decoupled from the services that fulfill orders.
I've been keeping it simple by dealing with a One-Way Message Exchange Pattern, but there are scenarios where you'll want to receive a response in return. In these situations, you want to look at the other contract types such as IRequestReplyRouter
which will allow you to do just that. The trouble is that if you have more than one service that receives the message,
and each responds back, which message should the Routing Service return back to the original client? These concerns are beyond the scope of this article which is why I've chosen to stick with one-way routing using the ISimplexDatagramRouter
, but they are important and you will encounter them as you start to want to take advantage of more advanced messaging patterns such as message aggregation/composition.
For now, I want to stick to a simple pub-sub messaging scenario that will help you conceptualize how further composition and aggregation can be built on top of this pattern.
Why: I want to deliver the same message to multiple services that are interested in the message.
How: Combine the Content-based Router (Hohpe, Woolf) pattern along with Message Router (Hohpe, Woolf) pattern and Message Filter (Hohpe, Woolf) pattern to make routing decisions based on the contents of a message and dispatch a copy of each message to all interested parties using the Publish Subscribe Channel (Hohpe, Woolf) pattern as depicted in Figure 10.
What: Implementing publish-subscribe at this point should feel natural because by this time you've worked with the foundational patterns that make it possible. The Message Broker pattern makes additional patterns such as Content-based Router and Message Filter possible, and the Publish Subscribe Channel pattern naturally builds on top of those to introduce additional receivers or subscribers.
My hope is that if you've gotten this far, your familiarity with the Routing Service configuration has also increased along the way. I've created the configuration in a file called web.config.PubSub
within the RoutingService
project. Rename the Web.config
file back to web.config.ContentBasedRouting
and change the name of Web.config.PubSub
to Web.config
.
As shown in Listing 6, I've reduced the number of service endpoints to one, but I've added four client endpoints that point to identical instances of the OneWayService
. I've also added four filters that include a filterType of MatchAll
and corresponding entries in the filter table. Of course, you can change the filterType
to anything you'd like, but for the purpose of illustrating pub-sub in the code sample, it will suit us just fine.
Listing 6: Routing Service Publish-Subscribe recipe
<system.serviceModel>
<services>
<service name="System.ServiceModel.Routing.RoutingService">
<endpoint name="MessageBroker"
address="MessageBroker"
binding="basicHttpBinding"
bindingConfiguration=""
contract="System.ServiceModel.Routing.ISimplexDatagramRouter" />
</service>
</services>
<client>
<endpoint address="http://localhost/OneWayService1"
binding="basicHttpBinding"
contract="*"
name="OneWayService1" />
<endpoint address="http://localhost/OneWayService2"
binding="basicHttpBinding"
contract="*"
name="OneWayService2" />
<endpoint address="http://localhost/OneWayService3"
binding="basicHttpBinding"
contract="*"
name="OneWayService3" />
<endpoint address="http://localhost/OneWayService4"
binding="basicHttpBinding"
contract="*"
name="OneWayService4" />
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<routing filterTableName="RoutingTable" routeOnHeadersOnly="False"/>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<routing>
<filters>
<filter name="OneWayService1Filter" filterType="MatchAll" />
<filter name="OneWayService2Filter" filterType="MatchAll" />
<filter name="OneWayService3Filter" filterType="MatchAll" />
<filter name="OneWayService4Filter" filterType="MatchAll" />
</filters>
<filterTables>
<filterTable name="RoutingTable">
<add filterName="OneWayService1Filter" endpointName="OneWayService1"/>
<add filterName="OneWayService2Filter" endpointName="OneWayService2"/>
<add filterName="OneWayService3Filter" endpointName="OneWayService3"/>
<add filterName="OneWayService4Filter" endpointName="OneWayService4"/>
</filterTable>
</filterTables>
</routing>
</system.serviceModel>
Publish-Subscribe in Action
Fire up all four instances of the OneWayService
by opening the OneWayServices
folder and double-clicking StartAll.cmd
. You should see four instances of the OneWayService
service, each listening on a unique address. This simulates four services that will potentially receive a message based on their interest, or subscription to a message/event processed by the Routing Service.
Now, run the client in Visual Studio, and as soon as you get the confirmation that the message has been sent, inspect each of the four console windows hosting each service. You will find that each and every service received the message as shown in Figure 11.
Keep in mind that in reality, you could have the Routing Service configured to handle multiple publish-subscribe scenarios. In this demo code, there is a single client and four subscribers, but you could theoretically have several subscribers opting in and out of messages based on filter criteria. You can get as granular in determining the subscription criteria using filters and content or context-based routing, or you can implement your own filter type.
This is a very powerful pattern that enables sophisticated messaging scenarios that lead to applications that are both loosely coupled and highly cohesive. I hope you'll consider how it can solve problems that perhaps before required lots of duplication, and brittle composition.
Summary
The Routing Service is a powerful addition to WCF which builds on the productivity theme of WCF 4. While I covered a lot of ground, there are several features in the Routing Service which I did not cover including support of additional contracts and message shapes, the ability to create custom filter types and support for dynamically updating a filter table based on an event such as a WS-Discovery announcement that a service has joined or left the network.
You've explored reasons why it may be a good idea to reconsider designing your service-oriented applications in a point to point manner, and how choosing the distributed application architectural pattern introduces many possibilities for sophisticated messaging scenarios that introduce newfound levels of flexibility in building applications that are loosely coupled and more cohesive.
I showed you how the Message Broker pattern really unlocks many of these possibilities, including providing high availability to your client applications as well as options for increasing the reach of your service including the ability to support multiple protocols on a service without modifying the service itself. You also saw how the context of a message, along with the contents within a message, can be used to make routing decisions.
Finally, I tied all of these patterns together by successively building on top of the Message Broker, Message Filter, and Content-based Routing design patterns to configure support for multiple services to subscribe to a message, realizing the highly coveted Publish Subscribe messaging pattern.
I hope that this scenario-driven approach will be helpful in understanding when you might apply a given design pattern and that the implementation patterns will serve you well in reaching beyond point to point in your next solution!