April 2009
Volume 24 Number 04
Foundations - Working With The .NET Service Bus
By Juval Lowy | April 2009
Contents
Relay Services to the Rescue
Relay Service Address
The Service Bus Bindings
The One-Way Relay Binding
The Event Relay Binding
Relay Service as an Event Hub
Looking Forward
The .NET Service Bus is arguably the most accessible, powerful, and useful piece of the new Azure Cloud Computing initiative. While the .NET Service Bus is designed to address some tough connectivity issues, it also provides an attractive solution to scalability, availability, and security issues by lowering the technology entrance barrier and making mainstream and mundane what used to be an advanced communication scenario. Specifically, the .NET Service Bus addresses the challenge of Internet connectivity. The truth is that Internet connectivity is difficult. Often, the service is located behind firewalls (both software and hardware firewalls), behind a load balancer; its address is dynamic and can be resolved only on the local network and cannot be translated to outside addressing. Virtualization adds a new dimension to connectivity along with maintaining transport sessions across machines. This situation is depicted in Figure 1.
Figure 1 Internet Connectivity Challenges
Moreover, since it is typically inadvisable to open the intranet to callers from the Internet, businesses often resort to using perimeter networks, but in turn incur the increased complexity of deployment, and of managing multiple sets of client credentials along with the unwanted traffic of unauthorized calls. Commonplace solutions for Internet connectivity from dynamic DNS to static IPs are often cumbersome, do not update in real time, but have scalability and throughput implications, and are potentially insecure unless built by experts.
When it comes to having the service call back to the client, the problem is compounded by all the connectivity issues surrounding the client, virtually a mirror image of Figure 1. And yet, callbacks, events, and peer-to-peer calls are often an integral part of many applications ranging from consumer machines hosting games, interactive communication, media sharing, and so on, to roaming business machines using ad hoc connections, to full-fledged cross-intranet business-to-business applications.
Relay Services to the Rescue
The solution to Internet connectivity challenges is simple—since it is so difficult to connect the client to the service directly, you can avoid doing that (at least initially) and instead use a relay service. The relay service is a service residing in the cloud, whose job is to assist in the connectivity, relaying the client calls to the service. Such a relay solution does require both the client and the service intranets to allow connections to the cloud, but since the cloud constitutes neutral territory for both the client and the service, most environments allow calls out to the Internet.
Figure 2shows how the relay service operates.
First, both the service and the client must establish connections and authenticate against the relay service. At this point, the relay also records where the service is and how to best call back to it. When the client calls the relay service, the relay service forwards the call (the client message) to the service.
Figure 2 The Relay Service In Action
While the sequence seems straightforward, in practice it involves a considerable amount of intricate network programming, messaging and standards know-how, security expertise, and more. Such a solution is simply out of reach for the vast majority of applications. This is exactly the gap the Microsoft .NET Service Bus is designed to fill. It is a ready-made relay service, hosted and managed at a Microsoft data center. The .NET Service Bus acts as a perimeter network in the cloud, providing a single place to manage credentials of the client and services. The .NET Service Bus is the front end of the service; it encapsulates and isolates the service from malicious callers lurking on the Internet and is responsible for repelling various attacks from denial-of-service to replay attacks, while obscuring the identity and true location of the actual service.
The main difference between connecting to a regular Windows Communication Foundation (WCF) service and using the relay service revolves around hosting. In the relayed case, the service must connect to the .NET Service Bus, authenticate itself, and listen to calls from the relay service before the client sends its requests. This means that you either must launch the host explicitly or use an NT Service as a host, and that you cannot benefit from hosting in Windows Activation Service (WAS) (or IIS) since WAS will launch the host only after the first request comes in, and that will never happen because the host has not connected to the .NET Service Bus in the first place.
The .NET Service Bus supports a WCF-friendly programming model by offering a set of dedicated bindings and behaviors. By and large, except for a few slight twists to the programming model, working with the relay service is no different than working with any other WCF service. The .NET Service Bus supports the core WCF features of reliable messaging, message security, and transport security. It does not support propagation of transactions from the client to the service across the relay service, although that may change in a future release. Beyond mere service endpoints, the .NET Service Bus also supports two additional related programming models: message queuing and message routing. I will address queuing and routing in a future column, since the combination enables yet another set of important design scenarios beyond Internet connectivity.
Relay Service Address
After opening an account with the .NET Service Bus, you need to use the .NET Service Bus administration Web site to create a new solution (see Figure 3). (Before you go any further, note that all the demos accompanying this column and the upcoming columns use MySolution as a placeholder for the solution name. To run the demos, you will need to create your own account with the .NET Service Bus, and replace MySolution with your solution name, and provide your solution credentials. To create an account, go to servicebus.windows.net.)
Figure 3 Creating a Solution
A solution is the equivalent of a machine or domain name in regular network addressing and any available URI-compliant string will do.
Like any other WCF service, every relayed service must have a unique address. The format of the address is always a base address followed by any number of optional URIs:
[base address]/[optional URI]/.../[optional URI]
The format of the base address is always prefixed by the transport schema, then the solution name followed by the service bus address:
[schema]://[solution]/[service bus address]
The service bus address is:
servicebus.windows.net
The format of the schema is either sb, http or https. Here are a few possible addresses:
sb://MySolution/servicebus.windows.net sb://MySolution/servicebus.windows.net/MyService sb://MySolution/servicebus.windows.net/MyService/MyEndpoint sb://MySolution/servicebus.windows.net/AnythingYouLike https://MySolution/servicebus.windows.net https://MySolution/servicebus.windows.net/MySolution/AnythingYouLike
Once your service connects to the relay service and starts listening on its address, no other service can listen on any other URI scoped under your service URI.
The Service Bus Bindings
The .NET Service Bus offers multiple bindings for relaying messages, yet the four main bindings I focus on here are the TCP relay bindings, the WS relay bindings, the one-way relay bindings, and the event relay binding (specifically, NetTcpRelayBinding, WSHttpRelayBinding, NetOnewayRelayBinding, and NetEventRelayBinding).
The TCP relay binding is the binding of choice in the majority of cases involving relayed Internet connectivity. It yields the best performance and throughput while minimizing the overhead for both the service and the relay service. It supports request-reply operations, one-way operations, and even duplex callbacks, all through the relay service. For schema, the TCP relay always uses sb:
<endpoint address = "sb://MySolution/servicebus.windows.net/..." binding = "netTcpRelayBinding" contract = "..."/>
The TCP relay binding requires TCP port 808 (or 828 when using transport security) to be open for calls out, something that most environments do support. The TCP relay binding offers unlimited message size (or at least up to the configured message size, as with the regular TCP binding). The TCP relay binding always maintains a transport session, so that with a session-full service, calls made on the same proxy channel always end up reaching the same service instance. However, as this binding uses the TCP binary encoding it is not interoperable—it assumes the other side is also using the TCP relay binding.
The TCP relay binding offers three connection modes: relayed, direct, and hybrid. You configure the connection mode using the TcpRelayConnectionMode enum and the ConnectionMode property of NetTcpRelayBindingBase, as you see in Figure 4.
Figure 4 TCP Relay Connection Modes
public enum TcpRelayConnectionMode { Relayed, Direct, Hybrid } public abstract class NetTcpRelayBindingBase : Binding,... { public TcpRelayConnectionMode ConnectionMode {get;set;} //More members } public class NetTcpRelayBinding : NetTcpRelayBindingBase {...}
When configured with TcpRelayConnectionMode.Relayed, all calls to the service always go through the relay. Relayed connection is the default mode of the TCP relay binding.
When configured with TcpRelayConnectionMode.Direct, the service first connects to the relay service and authenticated itself (step 1 in Figure 5), and then the client connects and authenticates itself (step 2 in Figure 5). However, at this point, the relay service will promote the connection to a direct connection between the client and the service, by telling the client how to reach the service directly (step 3 in Figure 5). With that is in place, the client can continue to call the service directly (step 4 in Figure 5).
Figure 5 Direct TCP Connection
The relay service will try to promote the connection to the most direct connection possible; that is, if the client and the service are part of the same intranet, it will provide the client with "better" coordinates; the same is true if they are on the same machine.
If a direct connection is impossible (typically if the relay service failed to correctly identify the service address), the connection is aborted. For direct connections, the service needs to also open up port 819 on its machine (something that may involve a one-time user prompt).
When the TCP relay binding is configured with TcpRelayConnectionMode.Hybrid, the communication starts as a relay via the relay service. If direct connection between the client and the service is possible, the relay service will promote it to direct connection. Otherwise, it will remain as relayed. The relay service can promote to direct even as the message is being relayed without any loss of data. The hybrid mode should be the preferred connection mode over the TCP relay binding. However, it has one drawback: both the direct and the hybrid modes require the binding to use Message security, and as you will see in the next installment of this column, Message security requires additional configuration and setup. And while these are simple steps, they do preclude hybrid as a working option by default.
I mentioned it already, but it's worth highlighting the fact the TCP relay binding supports duplex callbacks through the relay. Setting up the duplex calls and accessing the callback reference is identical to the regular TCP binding. Presently, the TCP relay binding is the only relay binding that supports duplex callbacks (see Figure 6).
Figure 6 Duplex TCP Relay Callback
The WS relay binding sends and receives interoperable WCF messages over HTTP (or HTTPS). Like the regular WS binding it uses text encoding by default, and when Message security or reliable messaging is employed, it will maintain the transport session over the relay. The scheme for the address is either http or https.
<endpoint address = "https://MySolution/servicebus.windows.net/..." binding = "wsHttpRelayBinding" contract = "..."/>
As far as capabilities and use, the WS relay binding is just like the TCP relay binding, except it supports only relayed connection. You should use this binding either when the TCP ports used by the TCP relay binding are blocked or when you have an explicit need to interoperate.
The One-Way Relay Binding
The one-way relay binding allows the client to send its message to a buffer maintained at the relay service, rather than the service itself, and later have the relay service try to deliver the message to the service. To control the load on the .NET Service Bus, messages sent are limited to 64KB (this limit may change in the future). No reply from the service is possible and in fact, the one-way relay binding verifies that all operations on the endpoint's contract are defined as one-way operations. In the case of both the TCP and the WS relay bindings, when the client calls the relay service, the service itself must be listening or the client will encounter an EndpointNotFoundExcpetion exception, just as with non-relayed WCF (this is the case even when the operations are defined as one-way). In the case of the one-way relay binding, the client can issue the call regardless of the presence of the service, and there may not even be a service listening at all. As a result, you should choose the one-way relay binding whenever the service's state is unknown. The downside is that the client has no guarantee of delivery of its messages, nor is there any guarantee of the order of the calls dispatched to the service. In addition, because of the inherent disconnected nature of the one-way relay binding, there is never a transport session. Just as with the TCP and the WS relay binding, there can be only one service monitoring the relayed address, while there could be any number of clients calling that service. For the schema, the one-way relay always uses sb:
<endpoint address = "sb://MySolution/servicebus.windows.net/..." binding = "netOnewayrelayBinding" contract = "..."/>
The Event Relay Binding
The event relay binding is a light but crucial specialization of the one-way relay binding:
public class NetEventRelayBinding : NetOnewayRelayBinding {...}
It allows any number of services to monitor the same URI in the relay service. Once a client sends a message to the relay, all monitoring services receive it. Given the fact there is no limitation on the number of clients, this in effect provides for N:M communication where both N and M can be any natural number including zero, similar to UDP multicast. Since the specialization is on the service side, the client can use either the one-way relay binding or the event relay binding while the services must use the event relay binding. In addition, unlike any other relay binding, you can also have services listening concurrently on nested URIs. As with the one-way relay binding, there is no guarantee of order of messages or of delivery itself.
Figure 7 Events Relay Service
By far, the canonical case for using the event relay binding is event publishing and subscription, as show in Figure 7. The clients, now called publishers, call the relay service now acting as an events hub, delivering the events to any number of services, now called subscribers.
Relay Service as an Event Hub
The design shown in Figure 7has the appearance of a general-purpose publish-subscribe pattern (for more on how to implement a full-fledged publish subscribe, see my article " WCF Essentials: What You Need To Know About One-Way Calls, Callbacks, And Events"). In reality, the events relay binding is indented to provide only a lightweight, ready-made, cloud-assisted event distribution solution for delivering events to running services. The more powerful features of administrative support for adding and removing subscribers, events filtering, and events queuing (to enable both queued publishers and queued subscribers) all require the use of queues and routers, which I will discuss in an upcoming column.
If you merely want to publish events to running subscribers over the relay service, then treating the relay service as your events hub will suffice. However, you'll soon run into the lack of support for discrete, operation-level events. Events equate to service endpoints, or more specifically, to the contract. There is no ability for the service to subscribe to a particular operation on the contract, but not to others. This means that the subscribing service still receives events it may not care about simply because it has a matching endpoint. For example, consider the IMyEvents contract:
[ServiceContract] interface IMyEvents { [OperationContract(IsOneWay = true)] void OnEvent1(); [OperationContract(IsOneWay = true)] void OnEvent2(int number); [OperationContract(IsOneWay = true)] void OnEvent3(int number,string text); }
If the subscriber defines the endpoint as in the following code, the subscriber will, of course, receive all calls to the endpoint.
<endpoint address = "sb://MySolution/servicebus.windows.net/IMyEvents" binding = "netEventRelayBinding" contract = "IMyEvents" />
If the subscriber cares only about receiving calls to the OnEvent2 operation, it must still expose an endpoint over the event relay binding, receive the calls to OnEvent2, but also receive all the unwanted traffic for OnEvent1 and OnEvent3 and filter them internally. This is a direct result of subscribing at the contract (or the endpoint) level and not at the discrete operation level.
The only way to manage events at the operation level is to map URIs to operations, not endpoints. Figure 8shows the publisher view of such endpoints.
Figure 8 Defining Events at the Operation Level
<endpoint name = "OnEvent1" address = "sb://MySolution/servicebus.windows.net/IMyEvents/OnEvent1" binding = "netOnewayBinding" contract = "IMyEvents" /> <endpoint name = "OnEvent2" address = "sb://MySolution/servicebus.windows.net/IMyEvents/OnEvent2" binding = "netOnewayBinding" contract = "IMyEvents" /> <endpoint name = "OnEvent3" address = "sb://MySolution/servicebus.windows.net/IMyEvents/OnEvent3" binding = "netOnewayBinding" contract = "IMyEvents" />
While the publishing client can easily consume the precise endpoint to fire a specific event, setting it up on the subscriber side is not straightforward. First you would want to allow subscribing and unsubscribing to individual events without disturbing other event processing in progress. Second, the subscriber cannot possibly open all the endpoints using the same host, since you would gain nothing; it would still get all the unwanted events. The only way to manage operation-level events is to have as many hosts as operations on the contract, all targeting the same service type. Each host will open a single endpoint, corresponding to a specific event (operation). To subscribe or unsubscribe to a particular event at run time, you must open or close the corresponding host. Because you cannot rely on listing the endpoints in the host config file (this will just make all hosts open all endpoints), you must programmatically add each desired endpoint to the specific host. as shown in Figure 9in pseudo code. The code in Figure 9sets up the endpoints exposed to the publisher in Figure 8.
Figure 9 Equating Events with Operations
class MySubscriber : IMyEvents {...} ServiceHost hostEvent1; ServiceHost hostEvent2; ServiceHost hostEvent3; Binding binding = new NetEventRelayBinding(); Uri baseAddress = new Uri("sb://MySolution/servicebus.windows.net/IMyEvents/"); //Subscribing to all events: hostEvent1 = new ServiceHost(typeof(MySubscriber),baseAddress); hostEvent1.AddServiceEndpoint(typeof(IMyEvents),binding,"OnEvent1"); hostEvent1.Open(); hostEvent2 = new ServiceHost(typeof(MySubscriber),baseAddress); hostEvent2.AddServiceEndpoint(typeof(IMyEvents),binding,"OnEvent2"); hostEvent2.Open(); hostEvent3 = new ServiceHost(typeof(MySubscriber),baseAddress); hostEvent3.AddServiceEndpoint(typeof(IMyEvents),binding,"OnEvent3"); hostEvent3.Open(); //Unsubscribe event 2: hostEvent2.Close();
However, such an approach is tedious, repetitive, and error prone. It is also tightly coupled to the event contract. To streamline, automate, and decouple this programming model I wrote the helper host EventRelayHost shown in Figure 10. The constructors of EventRelayHost all require at least one base addresses to use (with a regular host base addresses are optional).
Figure 10 EventRelayHost
public class EventRelayHost { public EventRelayHost(Type serviceType,string baseAddress); public EventRelayHost(Type serviceType,string[] baseAddresses); public void SetBinding(NetEventRelayBinding binding); public void SetBinding(string bindingConfigName) 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); public void Abort(); //More members }
You can also provide EventRelayHost with the binding to use via the SetBinding methods. EventRelayHost will default to a plain instance of NetEventRelayBinding. The Subscribe and Unsubscribe methods operate very much like the Open and Close methods of a regular host. You can subscribe (or unsubscribe) to all events on all service contracts supported by the service type, to all events on a particular service contract, or to a specific event operation on a particular service contract. Finally, you can call Abort to abort all events processing in progress and ungracefully shut down all hosts. You can see the implementation of EventRelayHost in Figure 11.
Figure 11 Implementing EventRelayHost
//Partial listing without error handling and security public class EventRelayHost { Dictionary<Type,Dictionary<string,ServiceHost>> m_Hosts = new Dictionary<Type,Dictionary<string, ServiceHost>>(); Type m_SericeType; string[] m_BaseAddresses; public void Subscribe(Type contractType,string operation) { m_Hosts[contractType] = new Dictionary<string,ServiceHost>(); List<Uri> baseAddressesList = new List<Uri>(); foreach(string address in m_BaseAddresses) { baseAddressesList.Add(new Uri(address + contractType)); } m_Hosts[contractType][operation] = new ServiceHost(m_SericeType, baseAddressesList.ToArray()); NetEventRelayBinding binding = new NetEventRelayBinding(); m_Hosts[contractType][operation]. AddServiceEndpoint(contractType, binding,operation); m_Hosts[contractType][operation].Open(); } public void Unsubscribe(Type contractType,string operation) { m_Hosts[contractType][operation].Close(); } //More members }
EventRelayHost maintains a dictionary that maps for each event service contract type another dictionary that maps for each operation a dedicated service host. The Subscribe method opens that host, and adds an endpoint for each base address. Subscribe appends for each endpoint address the contract type and the operation name. There is no need for a config file when using EventRelayHost, and the code in Figure 11is reduced to.
EventRelayHost host = new EventRelayHost(typeof(MyService), "sb://MySolution/servicebus.windows.net/..."); host.Subscribe(); ... host.Unsubscribe(typeof(IMyEvents),"OnEvent2");
While EventRelayHost streamlines the subscriber side, the client now has to maintain as many proxies as event endpoints. To help achieve that I wrote EventRelayClientBase<T>, declared as you see in Figure 12.
Figure 12 EventRelayClientBase<T>
public abstract class EventRelayClientBase<T> : IDisposable where T : class { public event EventHandler Closed; public event EventHandler Opened; public CommunicationState State {get;} public EventRelayClientBase(string solutionBaseAddress); public EventRelayClientBase(string solutionBaseAddress, NetEventRelayBinding binding) public void Open(); public void Close(); public void Abort(); public T Channel {get;} //More members }
To use EventRelayClientBase<T>, just derive from it like the regular ClientBase<T>, as I did in Figure 13.
Figure 13 Using EventRelayClientBase<T>
class MyEventsProxy : EventRelayClientBase<IMyEvents>,IMyEvents { public MyEventsProxy(string baseAddress) : base(baseAddress) {} public void OnEvent1() { Channel.OnEvent1(); } public void OnEvent2(int number) { Channel.OnEvent2(number); } public void OnEvent3(int number,string text) { Channel.OnEvent3(number,text); } }
EventRelayClientBase<T> offers quasi-support for ICommunicationObject's state and state change notifications. Unlike the regular proxy, EventRelayClientBase<T> requires the subscriber's base address. To that address, it appends the contract name and the operation name, to match the endpoints opened by EventRelayHost. As with the EventRelayHost, there is no need for a client-side config file. While EventRelayClientBase<T> looks to the client as a single proxy, it is in fact an aggregation of proxies—it maintains a proxy per endpoint on the subscriber side.
As shown in Figure 14, EventRelayClientBase<T> has a dictionary that maps an operation to an instance of T—the events service contract provided as a type parameter. When the client opens EventRelayClientBase<T>, it uses reflection to obtain a collection of all the event operations. For each operation, it appends to the base address the contract name and the operation name (to generate the endpoint address), and then uses the channel factory to create the matching proxy.
Figure 14 Implementing EventRelayClientBase<T>
//Partial listing without error handling and state management public abstract class EventRelayClientBase<T> : IDisposable where T : class { readonly string BaseAddress; Dictionary<string,T> m_Proxies = new Dictionary<string,T>(); public CommunicationState State {get;private set;} public void Open() { Binding binding = new NetOnewayRelayBinding(); MethodInfo[] methods = typeof(T).GetMethods(...); foreach(MethodInfo method in methods) { EndpointAddress address = EndpointAddress(new Uri(BaseAddress+typeof(T) + method.Name)); ChannelFactory<T> factory = new ChannelFactory<T>(binding, address); m_Proxies[method.Name] = factory.CreateChannel(); ICommunicationObject proxy = m_Proxies[method.Name] as ICommunicationObject; proxy.Open(); } State = CommunicationState.Opened; } public T Channel { get { if(State != CommunicationState.Opened) { Open(); } StackFrame frame = new StackFrame(1); return m_Proxies[frame.GetMethod().Name]; } } public void Close() { foreach(ICommunicationObject proxy in m_Proxies.Values) { proxy.Close(); } } public void Abort() { foreach(ICommunicationObject proxy in m_Proxies.Values) { proxy.Abort(); } } //More members }
The Channel property returns the correct proxy by accessing the stack frame, obtaining the name of the calling operation and using that as the key to look up the correct proxy from the dictionary. Closing EventRelayClientBase<T> closes all the proxies it manages.
Looking Forward
WCF also offers the BasicHttpRelayBinding, WebHttpRelayBinding, WS2007HttpRelayBinding, and WS2007FederationHttpRelayBinding. As their names imply, these bindings are the relayed equivalents of the regular WCF bindings. In addition, WCF provides the NetTcpContextRelayBinding, BasicHttpRelayContextBinding, and WebHttpRelayContextBinding. These context bindings all derive from their corresponding relay bindings but add support for the context protocol.
Security is also an issue. Both the client and the service should authenticate themselves against the relay service and protect the messages while on transfer from the client to the service. I will discuss service bus security, as well as the exciting queuing and routing, in my next columns.
Send your questions and comments to mmnet30@microsoft.com.
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 www.idesign.net.