Share via



October 2009

Volume 24 Number 10

Foundations - Routers in the Service Bus

By JUVAL LOWY | October 2009

In my previous two columns, I described the core problem the .NET service bus was designed to solve—that of Internet connectivity and related security issues. However, the service bus offers much more than mere connectivity and messaging relay. This column starts with the service registry and then describes aspects of routers. I’ll dedicate my next column to queues in the cloud. In both columns, I will describe new powerful design patterns, how to combine routers and queues, and my helper classes and tools to streamline the overall experience.

The Services Registry

The .NET service bus offers an ATOM feed of listening services on the solution base address or on any one of its sub-URIs. You can view the feed by navigating to the solution (or the URI) with a browser. The feed serves as a registry and a directory for the services and aspects (such as routers and queues) in the solution.

By default, your service is not visible in the browser. You control service registry publishing by using the ServiceRegistrySettings endpoint behavior class, which is defined as follows:

public enum DiscoveryType
{
Public,
Private
}
public class ServiceRegistrySettings : IEndpointBehavior
{
public ServiceRegistrySettings();
public ServiceRegistrySettings(DiscoveryType discoveryType);
public DiscoveryType DiscoveryMode
{get;set;}
public string DisplayName
{get;set;}
}

The host needs to add that behavior programmatically to every endpoint you want to publish to the registry. (There is no matching configurable behavior.) For example, to publish all endpoints to the registry, you would use the code shown here:

IEndpointBehavior registeryBehavior =
new ServiceRegistrySettings(DiscoveryType.Public);
ServiceHost host = new ServiceHost(typeof(MyService));
foreach(ServiceEndpoint endpoint in host.Description.Endpoints)
{
endpoint.Behaviors.Add(registeryBehavior);
}
host.Open();


Figure 1 The Services Bus Explorer

To help visualize the service bus, I developed the Service Bus Explorer, shown in Figure 1. The tool accepts the name of the solution to explore, and after logging onto the feed, will visualize for you the running services. All the explorer does is parse the ATOM feed and place the items in the tree to the left. You can explore multiple solutions and see in the right pane your solution administration pages. The tool also shows the available routers and queues and is very handy in administering them.

Cloud as Interceptor

The service bus was indeed initially developed to address the acute connectivity issues of calls across the Web. However, it has the potential for much more. Compare the basic Windows Communication Foundation (WCF) architecture with the service bus architecture. In both cases, the client does not interact directly with the service, but instead the calls are intercepted by middleware. In the case of regular WCF, the middleware is the proxy and the interpretation chain leading to the service, as shown in Figure 2.

In the case of the relayed calls, the middleware consists of the regular WCF middleware and the service bus itself, as shown in Figure 3. From an architecture standpoint, this is the same design—intercept the calls to provide additional value. In the current release of the service bus , that additional value is the ability to install routers and queues. In fact, I believe the service bus has great potential for powerful interceptors, and no doubt additional aspects will become available over time.


Figure 2 Intercepting Regular WCF Calls


Figure 3 The Cloud as Interceptor

Junctions as Routers

In the service bus, every URI in the solution is actually an addressable messaging junction. The client can send a message to that junction, and the junction can relay it to the services. However, each junction can also function as a router. Multiple services can subscribe to that router, and the router can forward the same client message to all of them or just a single one of them, as shown in Figure 4.

The client is decoupled from the services behind the router, and as far as the client is concerned, there may not even be any services behind the router. All the client needs to know is where the router it needs to send messages to is located. It trusts the router to be configured with the appropriate distribution policy. Because of that indirectness, the messages are inherently one way, and, in fact, WCF insists that the binding used by both the client and the subscribing services is the one-way relay binding. The client has no way of receiving the results from the services or of being apprised of service-side errors. This would be the case even if the binding were not one way. If the client message is being multiplexed to multiple services, then by definition there is no meaning for results returned from the operation (from which service?) or for errors (of which service?). There is also no way for the services to call back to their client.


Figure 4 The Services Bus as a Router

Router Policy

The solution administrator manages the routers independently of services. Each router must have a policy governing its behavior and lifetime. Out of the box, the solution administrator must perform programmatic calls to create and manage routers. All messaging junction policies derive from the abstract class JunctionPolicy, shown in Figure 5.

The Discoverability junction property is an enum of the type DiscoverabilityPolicy. It controls whether the junction is included in the ATOM feed of the solution. Discoverability defaults to DiscoverabilityPolicy.Managers, meaning a private policy. Setting it to DiscoverabilityPolicy.Public publishes it to the feed. The ExpirationInstant property controls the lifetime of the junction. Once the policy has expired, the junction is removed. ExpirationInstant defaults to one day. Obviously, that may or may not be adequate for your routers (or queues), so you typically need to set it to the value you want and monitor it. As the junction is about to expire, you should renew it if the junction is still in use. I call that renewing the “lease time” of the junction, and I call the party that extends the lease the “sponsor.” Finally, the TransportProtection property controls the transfer security of the message to the junction. When posting or retrieving raw WCF messages to or from the messaging junctions, you are restricted to using transport security with the value of TransportProtectionPolicy.AllPaths. Transport security is the default for all junction policies, and I recommend never setting it to any other value.

Figure 5 The JunctionPolicy Class

public enum DiscoverabilityPolicy
{
Managers,Public //More values
}
public enum TransportProtectionPolicy
{
None,AllPaths
}
[DataContract]
public abstract class JunctionPolicy
{
public JunctionPolicy();
public JunctionPolicy(JunctionPolicy policyToCopy);
public DateTime ExpirationInstant
{get;set;}
public DiscoverabilityPolicy Discoverability
{get;set;}
public TransportProtectionPolicy TransportProtection
{get;set;}
//More members
}

Figure 6 The RouterPolicy Class

public enum MessageDistributionPolicy
{
AllSubscribers,
OneSubscriber
}
[DataContract]
public class RouterPolicy : JunctionPolicy,...
{
public RouterPolicy();
public RouterPolicy(RouterPolicy otherRouterPolicy);
public int MaxSubscribers
{get;set;}
public MessageDistributionPolicy MessageDistribution
{get;set;}
//More members
}

Each router policy is expressed with the class RouterPolicy, defined in Figure 6. The two main properties here are MaxSubscribers and MessageDistribution. As its name implies, MaxSubscribers is the maximum number of concurrent subscribers allowed to connect to the router. The default value is 1, and the maximum value is 50. Once the router is maxed-out, additional services trying to subscribe get an error. MessageDistribution is an enum of the type MessageDistributionPolicy and defaults to MessageDistributionPolicy.OneSubscriber -- that is, only one of the subscribing services gets the message. Setting it to MessageDistributionPolicy.AllSubscribers delivers the message to all of the services.

Managing the Router Policy

You can use the RouterManagementClient class, shown here, to administer your router policies:

public static class RouterManagementClient
{
public static RouterClient CreateRouter(
TransportClientEndpointBehavior credential,
Uri routerUri,RouterPolicy policy);
public static void DeleteRouter(
TransportClientEndpointBehavior credential,
Uri routerUri);
public static RouterClient GetRouter(
TransportClientEndpointBehavior credential,
Uri routerUri);
public static RouterPolicy GetRouterPolicy(
TransportClientEndpointBehavior credential,
Uri routerUri);
public static DateTime RenewRouter(
TransportClientEndpointBehavior credential,
Uri routerUri,TimeSpan requestedExpiration);
}

RouterManagementClient is a static class, and all its methods require the credential object (of the type TransportClientEndpointBehavior, discussed in my previous column (see msdn.microsoft.com/magazine/dd942847.aspx). The following code demonstrates creating a simple router and a policy:

Uri routerAddress =
new Uri(@"sb://MySolution.servicebus.windows.net/MyRouter/");
TransportClientEndpointBehavior credential = ...;
RouterPolicy policy = new RouterPolicy();
policy.ExpirationInstant = DateTime.UtcNow + TimeSpan.FromMinutes(5);
policy.MaxSubscribers = 4;
policy.MessageDistribution = MessageDistributionPolicy.AllSubscribers;
RouterManagementClient.CreateRouter(credential,routerAddress,policy);

In the example, you instantiate a router policy object and set the policy to some values, such as distributing the message to all subscribing services and limiting the router to no more than four subscribers. The router is configured to have a short life of only five minutes. All it takes to install the router is to call the CreateRouter method of RouterManagementClient with the policy and some valid credentials.

As an alternative to programmatic calls, you can use my Services Bus Explorer to view and modify routers. You can create a new router by specifying its address and various policy properties. In much the same way, you can delete all routers in the solution.

You can also review and modify the policies of existing routers by selecting them in the solution tree and interact with their properties in the right pane, as shown in Figure 7.

All the client needs to do to post messages to a router is to create a proxy whose address is the router's address and call the proxy.

Subscribing to a Router

For a service to receive messages from a router, it must first subscribe to the router by adding to the host an endpoint whose address is the router’s address. One way of doing this is by using the helper class RouterClient, defined as follows:

public sealed class RouterClient
{
public ServiceEndpoint AddRouterServiceEndpoint<T>(
ServiceHost serviceHost);
//More members
}

The methods of RouterManagementClient that create or get a router return an instance of RouterClient. You need to call the AddRouterServiceEndpoint<T> method with an instance of your service host. You can also simply add an endpoint to the host (either programmatically or in a config file) whose address is the router's address. Here's an example:

<service name = "MyService">
<endpoint
address = "sb://MySolution.servicebus.windows.net/MyRouter/"
binding = "netOnewayRelayBinding"
contract = "..."
/>
</service>


Figure 7 A Router in the Services Bus Explorer


Figure 8 Subscribing Router to Router

Using Routers

Routers have three uses. The first is to broadcast the same message to multiple services. The classic case is, of course, publishing events to subscribing services, treating the service bus as an events-hub middleware. As mentioned in my column in the April issue (see msdn.microsoft.com/magazine/dd569756.aspx), there is more to event publishing than meets the eye, so I’ll defer discussing events to the end of this column.

The second use for a router is as a load balancer among services. If you configure the router policy to distribute messages to only a single subscriber, the router in effect acts as a load balancer among the subscribing services. All load balancers follow some algorithm in deciding which service will handle the next message. Common algorithms include round robin, random, some fair scoreboard and queuing. My testing indicates that the service bus uses a pseudo–round robin—that is, it was a fair round robin some 95 percent of the time. To streamline creating a load-balancing router, my helper class ServiceBusHelper offers a number of overloaded CreateLoadBalancingRouter methods, shown here:

public static partial class ServiceBusHelper
{
//Uses CardSpace for credential
public static void CreateLoadBalancingRouter(string balancerAddress);
/* Additional CreateLoadBalancingRouter(...)
with different credentials */
}

CreateLoadBalancingRouter creates a router with a policy that distributes the messages to a single subscriber. It also detects whether the router exists before creating it and automatically renews the router's lease. The implementation of CreateLoadBalancing Router is included in the article's code download.

All the public CreateLoadBalancingRouter methods take different credentials types (the implementation I provide uses CardSpace), construct an instance of TransportClientEndpointBehavior, and call an internal overloaded CreateLoadBalancingRouter. That overloaded version creates a load-balancing policy that also publishes the router to the ATOM feed and calls CreateRouter with the specified policy and credentials. CreateRouter first uses the RouterExists method to check whether the router has already been created. Unfortunately, the only way to check is to see whether an error occurs when you try to obtain the router's policy.

If the router exists, CreateRouter returns its corresponding RouterClient. If the router does not exist, CreateRouter first creates a timer to monitor the expiration of the lease, using a Lambda expression as a sponsor. As the lease time approaches its end, the timer calls the Lambda expression to renew the lease. CreateRouter then creates the router.

Routers to Routers

The third use for routers is to route messages to other junctions, such as other routers (or queues). To subscribe one router to another, use the SubscribeToRouter method of RouterClient, shown here:

public sealed class RouterClient
{
public RouterSubscriptionClient SubscribeToRouter(
RouterClient routerClient,
TimeSpan requestedTimeout);
//More members
}
[Serializable]
public class RouterSubscriptionClient
{
public DateTime Expires {get;}
public DateTime Renew(TimeSpan requestedExpiration,
TransportClientEndpointBehavior credential);
public void Unsubscribe(TransportClientEndpointBehavior credential);
//More members
}

SubscribeToRouter accepts the router client of the router you want to subscribe to and returns an instance of RouterSubscriptionClient. The main uses of RouterSubscriptionClient is to unsubscribe and to renew the subscription lease.

For example, suppose you have service A, service B, and service C. You require that every client call always goes to service A and either service B or service c. (Imagine that you need to load balance service B and service C but also keep a record of all calls in the logbook service A). The router policies described so far, of either delivering to all subscribers or to a single subscriber, are inadequate. However, you can satisfy the requirements easily by using two routers, as shown in Figure 8.

The client delivers the messages to a top-level router with a policy of distribution to all services. Service A subscribes to the top-level router. A subrouter with a distribution policy of a single subscriber also subscribes to the top-level router. Both service B and service C subscribe to that subrouter. Figure 9 shows the required routers configuration and how to subscribe the subrouter to the top router.

When it comes to a service subscribing to a subscribing router, there is one important detail regarding the subscription that differs from subscribing to a top-level router. The service endpoint must indicate that it is willing to receive messages from the subscribing router, even though the messages are addressed to the top-level router. This indication is made by setting the listening URI property of the endpoint to the subscribing service, while having the service endpoint itself address the top-level router. Using the addresses in Figure 9, both service B and service C would have to define their endpoints as follows:

<service name = "MyService">
<endpoint listenUri =
"sb://MySolution.servicebus.windows.net/MySubRouter/"
address = "sb://MySolution.servicebus.windows.net/MyTopRouter/"
binding = "netOnewayRelayBinding"
contract = "..."
/>
</service>

You can automate this setting with the following service host extension:

public static partial class ServiceBusHelper
{
public static void SubscribeToRouter(this ServiceHost host,
string subRouterAddress)
{
for(int index = 0;
index < host.Description.Endpoints.Count;index++)
{
host.Description.Endpoints[index].ListenUri =
new Uri(subRouterAddress);
}
}
//More members
}

Given that a router can subscribe to multiple routers, the services that subscribe to the subscribing router should be very mindful of which address -- that is, which top-level router -- they want to receive messages from.

Note that removing the top-level router or terminating the subscription cuts off the subrouters from client messages. This, in turn, is a feature of sorts for solution administrators, who can use this step to effectively shut off services without closing down their hosts.

Figure 9 Subscribing Router to Router

Uri topRouterAddress =
new Uri(@”sb://MySolution.servicebus.windows.net/MyTopRouter/");
Uri subRouterAddress =
new Uri(@”sb://MySolution.servicebus.windows.net/MySubRouter/");
TransportClientEndpointBehavior credential = ...;
RouterPolicy topRouterPolicy = new RouterPolicy();
subRouterPolicy.MaxSubscribers = 20;
topRouterPolicy.MessageDistribution =
MessageDistributionPolicy.AllSubscribers;
RouterClient topRouterClient = RouterManagementClient.CreateRouter(
credential,topRouterAddress,topRouterPolicy);
RouterPolicy subRouterPolicy = new RouterPolicy();
subRouterPolicy.MaxSubscribers = 30;
subRouterPolicy.MessageDistribution =
MessageDistributionPolicy.OneSubscriber;
RouterClient subRouterClient = RouterManagementClient.CreateRouter(
credential,subRouterAddress,subRouterPolicy);
RouterSubscriptionClient subscription = subRouterClient.
SubscribeToRouter(topRouterClient,
TimeSpan.MaxValue);
//Sometime later
subscription.Unsubscribe(credential);

Using Routers for Events

As I mentioned previously, the messaging junction in the service bus can act as a crude events hub and broadcast the messages to subscribing services. You need to install a router policy that maximizes the number of subscribers and notifies all subscribers. The shortcomings of doing so are similar to using the events binding (discussed in my column in April at msdn.microsoft.com/magazine/dd569756.aspx). There is no per-operation subscription, and consequently the subscribers (all of them) always receive all events, even events they do not care about (those that simply have a matching endpoint).

The solution is not to create a single router to handle all events but to create a router per event. Services that are interested in just that event will subscribe to that event’s particular router. As with the events binding, having a router per event is accomplished by having each subscriber manage a host instance per operation, because a single host that monitors all routers will do nothing to filter the events. The subscriber needs to open and close the respective host to subscribe or unsubscribe to the event. This requires tedious and repetitive code.

The good news is that the helper class EventsRelayHost that I presented in my previous column is already doing just that, and with a little refactoring of a common base class called EventsHost, it can be adapted for use with routers rather than the events relay binding. (For routers, you need just the one-way relay binding.)

First, I defined the abstract base class EventsHost, shown here:

//Partial listing
public abstract class EventsHost
{
public EventsHost(Type serviceType,string baseAddress);
public EventsHost(Type serviceType,string[] baseAddresses);
public abstract void SetBinding(NetOnewayRelayBinding binding);
public abstract void SetBinding(string bindingConfigName)
protected abstract NetOnewayRelayBinding GetBinding();
public void Subscribe();
public void Subscribe(Type contractType);
public void Subscribe(Type contractType,string operation)
public void Unsubscribe();
public void Unsubscribe(Type contractType);
public void Unsubscribe(Type contractType,string operation);
}

The two subclasses, EventsRelayHost and EventsRouterHost, are defined in Figure 10.The implementation of EventsHost and EventsRelayHost is presented in my previous column.

Figure 10 Defining EventsRelayHost

//For use with the events binding, see previous column
public class EventsRelayHost : EventsHost
{...}
public class EventsRouterHost : EventsHost
{
public override void SetBinding(NetOnewayRelayBinding binding)
{
RelayBinding = binding;
}
public override void SetBinding(string bindingConfigName)
{
SetBinding(new NetOnewayRelayBinding(bindingConfigName));
}
protected override NetOnewayRelayBinding GetBinding()
{
return RelayBinding ?? new NetOnewayRelayBinding();
}
}

EventsRouterHost is similar to EventsRelayHost -- both manage a host per event, and it matters not if the address they monitor is the address of an events endpoint or that of a router. The only difference is that the EventsRouterHost must use the one-way relay binding to receive the events, as opposed to the events binding used by EventsRelayHost. Other than that, EventsRouterHost and EventsRelayHost should be identical. This is reflected in the fact that both derive from EventsHost, which does the heavy lifting of managing the hosts. Both EventsRouterHost and EventsRelayHost merely override the binding management methods.

Other than that, using EventsRouterHost is just like using EventsRelayHost, where Subscribe and Unsubscribe function instead of opening and closing the host:

EventsHost host = new EventsRouterHost(typeof(MyService),
"sb://MySolution.servicebus.windows.net/...");
host.Subscribe();
...
host.Unsubscribe(typeof(IMyEvents),"OnEvent2");

The previous code opens and closes a respective host internally that monitors just that event.

Creating Event Routers

EventsRouterHost requires the solution administrator to configure the routers beforehand, and it will not try to create the routers. I made that choice deliberately to separate configuring the routers and their policies from the subscribers. All the client sees is the junction’s address, and to fire an event over a one-way relay binding, you can use the same EventRelayClientBase presented in my previous column to publish the events.

To streamline the effort of creating the event routers, you can use my ServiceBusHelper to create the routers:

public static partial class ServiceBusHelper
{
//Uses CardSpace for credential
public static RouterClient[] CreateEventRouters(string baseAddress,
Type contractType);
/* Additional CreateEventRouters(...) with different credentials */
}

CreateEventRouters accepts the router base address. It appends to the base address the name of the contract and the operation name and opens an individual router under that address. For example, given the following base address:

sb://MySolution.servicebus.windows.net/

and this contract definition:

[ServiceContract]
interface IMyEvents
{
[OperationContract(IsOneWay = true)]
void OnEvent1();
[OperationContract(IsOneWay = true)]
void OnEvent2(int number);
[OperationContract(IsOneWay = true)]
void OnEvent3(int number,string text);
}

CreateEventRouters creates the following routers:

sb://MySolution.servicebus.windows.net/IMyEvents/OnEvent1/
sb://MySolution.servicebus.windows.net/IMyEvents/OnEvent2/
sb://MySolution.servicebus.windows.net/IMyEvents/OnEvent3/

This is exactly what EventsRouterHost expects. This article's code download provides a partial listing of the implementation of ServiceBusHelper.CreateEventRouters, without error handling and a few of the security overloaded methods.

The public CreateEventRouters that takes no credentials defaults to using CardSpace by creating a TransportClientEndpointBehavior object and passing it to the internal method. That method uses reflection to obtain the collection of operations on the service contract type. For each operation, it calls the CreateEventRouter method, passing it the address for the router and the policy. The policy for each router maxes out the number of subscribers, delivers the events to all of them and publishes the router to the ATOM feed.

Routers vs. the Events Binding

For most cases of cloud-based, publish-subscribe solutions, I would recommend the events binding. First, I find the limit on the maximum number of subscribers to be a serious handicap. It is possible that 50 will be more than adequate for your application. However, what if over time you need to support more subscribers? No such limit is imposed on the events binding. Other issues with the routers approach are the need to create the routers beforehand and to worry about lease sponsorship. The events binding, on the other hand, functions as a crude ad-hoc router without these liabilities.

There are two saving graces for router-based events management. The first is the option for the system administrators to use tools such as the Services Bus Explorer to manage the subscribers, even indirectly by managing the routers. The second, and more ubstantial, is the ability to combine routers with queues to create queued publishers and queues subscribers, the approach I will describe in my next column.


Juval Lowy* is a software architect with IDesign providing WCF training and architecture consulting. His recent book is “Programming WCF Services, 2nd Edition” (O’Reilly, 2008). He is also the Microsoft regional director for the Silicon Valley. Contact Juval at idesign.net.*