Design Patterns, Practices, and Techniques with the Azure AppFabric Service Bus Juval Lowy IDesign www.idesign.net ©2011 IDesign Inc. All rights reserved About Juval Löwy Software architect Consults and trains on .NET architecture and technology Microsoft's Regional Director for the Silicon Valley Recent book Programming WCF Services 3rd Edition (2010 O’Reilly) Participates in the .NET/WCF design reviews Publishes at MSDN and other magazines Recognized Software Legend by Microsoft Contact at www.idesign.net Agenda What is the service bus Brief Techniques Publishing to registry Discrete events Discovery Explorer Structured buffers Response service Bonus Material Time permitting Azure AppFabric Service Bus While designed to address connectivity issues also provides Scalability Availability Security Lower technology entrance barrier by making advanced scenario main-stream and mundane Why Service Bus Internet connectivity is hard Firewalls Load balancers Sessions NAT Private IPs Discovery and registry Virtualization Proxy servers LB NAT Router H/W Firewall S/W Firewall Client Service Security IT in general … Why Service Bus Scalability throughput and availability challenges Opening your Intranet Requires DMZ Clients credentials management Commonplace solutions Cumbersome Not real-time Potentially insecure Why Service Bus Solution Do not connect clients to services directly At least not initially Use a relay service Relay service in the cloud Neutral territory Only requires outbound calls to establish connection Allowed in most environments Will relay client calls to service Additional benefits of scalability, security, administration Service Bus Relay 1. Service connects and authenticates against relay Relay figures out how to best call back to service 2. Client connects and authenticates against relay 3. Client sends message to service 4. Relay forwards message to service Relay Service 2 3 Client 1 4 Service Azure AppFabric Service Bus Ready-made service bus relay service DMZ in the sky Only allows authenticated authorized calls Repels attacks and hides service Relay service in cloud routing messages Microsoft massive data centers Additional features Security Access rules Service registry Relay Service Address Address format [base address]/[optional URI]/.../[optional URI] Bus address format [schema]://[namespace].servicebus.windows.net/ Schema format is sb or http or https Binding dependent Services Registry Can view ATOM feed of listening services in namespace Or one of its sub URI http://[namespace].servicebus.windows.net/[URI] Services Registry Can control publishing to registry 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;} } Services Registry Must use programmatic setting 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(); Discoverable Host My DiscoverableServiceHost automates registry Used like regular host public class DiscoverableServiceHost : ServiceHost,IServiceBusProperties { public DiscoverableServiceHost(object singletonInstance,params Uri[] baseAddresses); public DiscoverableServiceHost(Type serviceType,params Uri[] baseAddresses); //More members } IServiceBusProperties All my service bus helpers support my IServiceBusProperties public interface IServiceBusProperties { TransportClientEndpointBehavior Credential {get;set;} Uri[] Addresses {get;} } Service Bus Explorer View with my Service Bus Explorer Can administer buffers as well Service Bus Bindings The three main bindings NetTcpRelayBinding NetOnewayRelayBinding NetEventRelayBinding TCP Relay Binding Binding of choice in most cases Best performance and throughput Minimum overhead for service Unlimited message size Up to configured limits Request-reply messages through relay TCP Relay Binding Multiple clients / single service As with regular WCF Maintains transport session Clients gets the same instance Not interoperable Uses sb for transport <endpoint address = "sb://MyNamespace.servicebus.windows.net/..." binding = "netTcpRelayBinding" contract = "..." /> One-Way Relay Binding No reply messages All operations must be one-way Message goes into a buffer Messages limited to 64 KB Used sb for scheme <endpoint address = "sb://MyNamespace.servicebus.windows.net/..." binding = "netOnewayrelayBinding" contract = "..." /> Event Relay Binding Specialization of one-way relay public class NetEventRelayBinding : NetOnewayRelayBinding {...} Allows any number of services to monitor buffer N:M communication Can safely listen concurrently on nested URIs For whatever reason Clients may still call over NetOnewayRelayBinding Services must use NetEventRelayBinding Service Bus as Events Hub Publisher Publisher Events Hub Subscriber Subscriber Subscriber Service Bus as Events Hub Light weight pub/sub system No administrative support No per-operation subscription Endpoint level only Service Bus as Events Hub Subscriber still receives events it may not care about simply because it has a matching endpoint [ServiceContract] interface IMyEvents { [OperationContract(IsOneWay = true)] void OnEvent1(); [OperationContract(IsOneWay = true)] void OnEvent2(int number); [OperationContract(IsOneWay = true)] void OnEvent3(int number,string text); } Service Bus as Events Hub To manage events at the operation level need to map URIs to operations not endpoints <endpoint name = "OnEvent1" address = "sb://MyNamespace.servicebus.windows.net/IMyEvents/OnEvent1" binding = "netOnewayBinding" contract = "IMyEvents" /> <endpoint name = "OnEvent2" address = "sb://MyNamespace.servicebus.windows.net/IMyEvents/OnEvent2" binding = "netOnewayBinding" contract = "IMyEvents" /> <endpoint name = "OnEvent3" address = "sb://MyNamespace.servicebus.windows.net/IMyEvents/OnEvent3" binding = "netOnewayBinding" contract = "IMyEvents" /> Service Bus as Events Hub Have as many hosts as subscribed operations all targeting same service type At run-time must recycle hosts and programmatically add each desired endpoint to specific host Tedious repetitive code Expensive Pay for connections Service Bus as Events Hub Streamline with my ServiceBusEventsHost public class ServiceBusEventsHost : DiscoverableServiceHost { public ServiceBusEventsHost(Type serviceType,Uri baseAddress); public ServiceBusEventsHost(Type serviceType,Uri[] baseAddresses); /*Additional constructors */ //Can optionally specify binding public virtual NetOnewayRelayBinding RelayBinding {get;set;} public void SetBinding(string bindingConfigName); //Subscription management 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); } Service Bus as Events Hub ServiceBusEventsHost used like regular host Requires base address(es) Appends contract name to each base address Can accept binding to use Defaults for secure binding No need for config file Can look up binding from config Can subscribe or unsubscribe all operations on all contracts Can subscribe or unsubscribe all operations on contract Can subscribe or unsubscribe specific operation on 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); } class MySubscriber: IMyEvents {...} string baseAddress = "sb://MyNamespace.servicebus.windows.net/"; ServiceBusEventsHost host = new ServiceBusEventsHost(typeof(MySubscriber),baseAddress); host.Open(); host.Subscribe(); host.Unsubscribe(typeof(IMyEvents),"OnEvent2"); host.Subscribe(); host.Unsubscribe(); Service Bus as Events Hub Subscriptions stored in dictionary Maps subscribed operations per contract Can add admin support Use a tool to manage subscriptions outside scope of host/service Manage subscriptions against host as needed Service Bus as Events Hub ServiceBusEventsHost adds endpoint per base address per contract [base address]/[contract name] Monitors all messages to that address and below Publishers send messages to endpoint whose address contains operation name [base address]/[contract name]/[operation] Service Bus as Events Hub ServiceBusEventsHost uses operation selector interceptor to rout message to subscribed operation Attached as endpoint behavior to all dispatchers public interface IDispatchOperationSelector { string SelectOperation(ref Message message); } //Partial listing without error handling public class ServiceBusEventsHost : ServiceBusHost { //Managing the subscriptions Dictionary<string,List<string>> Subscriptions {get;set;} public ServiceBusEventsHost(Type serviceType,Uri[] baseAddresses) : base(serviceType,baseAddresses) { Subscriptions = new Dictionary<string,List<string>>(); foreach(Uri baseAddress in BaseAddresses) { Type[] contracts = GetServiceContracts(); foreach(Type contract in contracts) { AddServiceEndpoint(contract,RelayBinding, baseAddress.AbsoluteUri + contract); Subscriptions[contract.Name] = new List<string>(); } } IEndpointBehavior selector = new EventSelector(Subscriptions); foreach(ServiceEndpoint endpoint in Description.Endpoints) { endpoint.Behaviors.Add(selector); } } public void Subscribe(Type contractType,string operation) { if(Subscriptions[contractType.Name].Contains(operation) == false) { Subscriptions[contractType.Name].Add(operation); } } public void Unsubscribe(Type contractType,string operation) { if(Subscriptions[contractType.Name].Contains(operation)) { Subscriptions[contractType.Name].Remove(operation); } } //Uses reflection to get all service contracts Type[] GetServiceContracts() {...} class EventSelector : IDispatchOperationSelector,IEndpointBehavior { readonly Dictionary<string,List<string>> m_Subscriptions; public EventSelector(Dictionary<string,List<string>> subscriptions) { m_Subscriptions = subscriptions; } public string SelectOperation(ref Message message) { string[] slashes = message.Headers.Action.Split('/'); string contract = slashes[slashes.Length-2]; string operation = slashes[slashes.Length-1]; if(m_Subscriptions[contract].Contains(operation)) return operation; else return null; } void IEndpointBehavior.ApplyDispatchBehavior( ServiceEndpoint endpoint,EndpointDispatcher endpointDispatcher) { endpointDispatcher.DispatchRuntime.OperationSelector = this; } ... } } Service Bus as Events Hub Publisher can use plain one-way relay proxy Careful to match expected endpoints layout Automate proxy with my ServiceBusEventsClientBase Appends contract name and operation to base address No need for config file Only needs base address Always uses one way relay binding Used like regular proxy public abstract class ServiceBusEventsClientBase<T> : ...ClientBase<T> where T : class { public ServiceBusEventsClientBase(string baseAddress) : this(baseAddress,new NetOnewayRelayBinding()) {} public ServiceBusEventsClientBase(string baseAddress,NetOnewayRelayBinding binding) : base(binding,ToEventAddress(baseAddress)) {} /* More constructors */ static EndpointAddress ToEventAddress(string baseAddress) { return new EndpointAddress(baseAddress + typeof(T).Name); } } Service Bus as Events Hub class MyEventsProxy : ServiceBusEventsClientBase<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); } } Discovery Discovery Discovery was designed for the Intranet Discovery is useful Loosely-coupled clients and services Dynamic addresses Easy deployment Service bus may support discovery in future Discovery Would be nice to combine benefit of loose deployment of discovery with unhindered connectivity of service bus Can substitute events binding for UDP Discovery requests Announcements Mimic WCF discovery behavior Solution Architecture Streamline with my helpers IServiceBusDiscovery for discovery requests Events relay binding Supported by discoverable services Provides reply address of client [ServiceContract] public interface IServiceBusDiscovery { [OperationContract(IsOneWay = true)] void DiscoveryRequest(string contractName,string contractNamespace, Uri[] scopesToMatch,Uri responseAddress); } Solution Architecture IServiceBusDiscoveryCallback for receiving services responses Exposed by clients Over one-way relay binding [ServiceContract] public interface IServiceBusDiscoveryCallback { [OperationContract(IsOneWay = true)] void DiscoveryResponse(Uri address,string contractName,string contractNamespace, Uri[] scopes); } Client Operation Event 3 1 IServiceBusDiscovery Discovery Requests Relay Service Relay Service IServiceBusDiscoveryCallback 2 Service Discoverable Host My DiscoverableServiceHost Used like regular host To enable must add discovery behavior and discovery endpoint Forward looking and compatible Discoverable Host public class DiscoverableServiceHost : ServiceHost,IServiceBusProperties { public Uri DiscoveryAddress {get;set;} public NetEventRelayBinding DiscoveryRequestBinding {get;set;} public NetOnewayRelayBinding DiscoveryResponseBinding {get;set;} public DiscoverableServiceHost(object singletonInstance,params Uri[] baseAddresses); public DiscoverableServiceHost(Type serviceType,params Uri[] baseAddresses); } Discoverable Host Creates internal host for private service class DiscoveryRequestService implementing IServiceBusDiscovery Monitors discovery requests Address defaults to URI "DiscoveryRequests" Configurable via DiscoveryAddress property Uses plain events binding to receive requests Configurable via DiscoveryRequestBinding property Calls back client using IServiceBusDiscoveryCallback Uses plain one way binding Configurable via DiscoveryResponseBinding property Discoverable Host Uri baseAddress = new Uri("sb://..."); ServiceHost host = new DiscoverableServiceHost(typeof(MyService,baseAddress); //Address is dynamic host.AddServiceEndpoint(typeof(IMyContract),new NetTcpRelayBinding(), Guid.NewGuid().ToString()); host.Open(); Discovery Client For client use my ServiceBusDiscoveryClient Modeled after DiscoveryClient public class ServiceBusDiscoveryClient : ClientBase<IServiceBusDiscovery>, IServiceBusProperties { protected Uri ResponseAddress {get;} public ServiceBusDiscoveryClient(string serviceNamespace,...); public ServiceBusDiscoveryClient(string endpointName); public ServiceBusDiscoveryClient(NetOnewayRelayBinding binding,EndpointAddress address public FindResponse Find(FindCriteria criteria); } Discovery Client ServiceBusDiscoveryClient is proxy for IServiceBusDiscovery Defaults address to URI "DiscoveryRequests" Can provide constructors with different address Uses plain one-way binding to fire requests and receive responses Can provide constructors with different binding Supports cardinality and timeouts Find() hosts an internal service supporting IServiceBusDiscoveryCallback Opens and closes host per call Discovery Client string serviceNamespace = "..."; ServiceBusDiscoveryClient discoveryClient = new ServiceBusDiscoveryClient(serviceNamespace,...); FindCriteria criteria = new FindCriteria(typeof(IMyContract)); FindResponse discovered = discoveryClient.Find(criteria); discoveryClient.Close(); EndpointAddress address = discovered.Endpoints[0].Address; Binding binding = new NetTcpRelayBinding(); ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract> (binding,address); IMyContract proxy = factory.CreateChannel(); proxy.MyMethod(); (proxy as ICommunicationObject).Close(); Service Bus Discovery Helper Helper class public static class ServiceBusDiscoveryHelper { public static EndpointAddress DiscoverAddress<T>( string serviceNamespace,...,Uri scope = null); public static EndpointAddress[] DiscoverAddresses<T>( string serviceNamespace,...,Uri scope = null); public static Binding DiscoverBinding<T>( string serviceNamespace,...,Uri scope = null); } Service Bus Discovery Factory Helper factory public static class ServiceBusDiscoveryFactory { public static T CreateChannel<T>(string serviceNamespace,...,Uri scope = null) where T : class; public static T[] CreateChannels<T>(string serviceNamespace,...,Uri scope = null) where T : class; } Announcements Can use events binding to support announcements My IServiceBusAnnouncements [ServiceContract] public interface IServiceBusAnnouncements { [OperationContract(IsOneWay = true)] void OnHello(Uri address,string contractName,string contractNamespace,Uri[] scopes); [OperationContract(IsOneWay = true)] void OnBye(Uri address,string contractName,string contractNamespace,Uri[] scopes); } Client Operation Event 2 Announcements Relay Service IServiceBusAnnouncements 1 Service Announcements Automated with DiscoverableServiceHost public class DiscoverableServiceHost : ServiceHost,IServiceBusProperties { public Uri AnnouncementsAddress {get;set;} public NetOnewayRelayBinding AnnouncementsBinding {get;set;} //More members } Announcements Requires configuring announcements endpoint in discovery behavior Defaults to announcing on "AvailabilityAnnouncements" URI under service namespace Configurable via AnnouncementsAddress property Calls client using plain one way binding Configurable via AnnouncementsBinding property Fires event asynchronously Announcements Client uses my ServiceBusAnnouncementSink<T> [ServiceBehavior(UseSynchronizationContext = false, InstanceContextMode = InstanceContextMode.Single)] public class ServiceBusAnnouncementSink<T> : AnnouncementSink<T>,IServiceBusAnnouncements ,IServiceBusProperties where T : class { public ServiceBusAnnouncementSink(string serviceNamespace,string secret); public ServiceBusAnnouncementSink(string serviceNamespace,string owner,string secret); public Uri AnnouncementsAddress {get;set;} public NetEventRelayBinding AnnouncementsBinding {get;set;} } Announcements Supports IServiceBusAnnouncements Hosted as singleton published to registry Subscribes to events Defaults to "AvailabilityAnnouncements" URI Configurable via AnnouncementsAddress property Uses plain events binding Configurable via AnnouncementsBinding property Client can subscribe to events or access addresses container class MyClient { AddressesContainer<IMyContract> m_Addresses; public MyClient() { string serviceNamespace = "..."; string secret = "..."; m_Addresses = new ServiceBusAnnouncementSink<IMyContract>(serviceNamespace,secret); m_AnnouncementSink.Open(); ... } void OnCallService(object sender,EventArgs e) { EndpointAddress address = m_Addresses[0]; IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel( new NetTcpRelayBinding(),address); proxy.MyMethod(); (proxy as ICommunicationObject).Close(); } ... MEX Explorer Revamped to use service bus discovery and announcements Buffers Service Bus Buffers Queued calls over the Internet Almost Every junction in the service bus can host a buffer Sender Reader Service Bus Buffers Buffer does not equate queue Not durable Data loss with catastrophic crash of service bus Not transactional Not long lasting messages Max is 10 minutes Buffers are limited Up to 50 messages In between queued calls and fire-and-forget calls Service Bus Buffers Aimed at Async calls Chunky calls Mostly connected applications on somewhat shaky connections One-way calls Elastic Internet wire Service Bus Buffers All messages are one-way No results No errors No callbacks Service Bus Buffers Can manage and create buffers with the my Explorer Service Bus Buffers Can manage and create buffers with my Explorer Buffered Services The service bus requires working with raw WCF messages Raw messages are Cumbersome Not object-oriented Not type safe Should convert between service calls and raw messages Requires a lot of low level advanced work Buffered Services Automate with my BufferedServiceBusHost<T> Used like a regular host Modeled after MSMQ binding public class BufferedServiceBusHost<T> : ServiceHost<T>,... { public BufferedServiceBusHost(params Uri[] bufferAddresses); public BufferedServiceBusHost(T singleton,params Uri[] bufferAddresses); /* Additional constructors */ } Uri buffer = new Uri(@"https://MyNamespace.servicebus.windows.net/MyBuffer"); ServiceHost host = new BufferedServiceBusHost<MyService>(buffer); host.Open(); Buffered Services For client use BufferedServiceBusClient<T> [ServiceContract] interface IMyContract { [OperationContract(IsOneWay = true)] void MyMethod(int number); } class MyContractClient : BufferedServiceBusClient<IMyContract>,IMyContract { public void MyMethod(int number) { ... } } Response Service No way to get Results Errors Client can provide dedicated response buffer for service Should pass response address and method ID in headers Automate with my framework Response Service Service Buffer Client Service Response Buffer Response Service Bonus Material Service Bus Security Security Aspects Service bus authentication Message transfer security Service Bus Authentication Service must authenticate to connect to service bus Client may or may not authenticate Typically should Application authentication Not user authentication Service Bus Authentication Administrator uses portal to assign and create tokens Service Bus Authentication Tokens both authenticate and authorize Sending messages Reading messages Manage service namespace Service and client may not use the same token Service Bus Authentication Can streamline with my extension method Defaults to owner as issuer public static class ServiceBusHelper { public static void SetServiceBusCredentials(this ServiceHost host,string secret); ... } ServiceHost host = new ServiceHost(typeof(MyService)); host.SetServiceBusCredentials("QV3...9M8="); host.Open(); Service Bus Authentication Client side public static partial class ServiceBusHelper { public static void SetServiceBusCredentials<T>(this ClientBase<T> proxy, string secret) where T : class; public static void SetServiceBusCredentials<T>( this ChannelFactory<T> factory,string secret) where T : class; ... } MyContractClient proxy = new MyContractClient(); proxy.SetServiceBusCredentials("QV3...9M8="); proxy.MyMethod(); proxy.Close(); Transfer Security Securely transfer the message from client to service What degree of client identity is in the message Four modes for transfer security None Transport Message Mixed public enum EndToEndSecurityMode { None, Transport, Message, TransportWithMessageCredential //Mixed } Transport Security Transfer to relay and from relay is secure TCP uses SSL WS uses HTTPS Simplest and easiest to set up and use Will never contain client's credentials All calls are anonymous Transport Security Journey inside relay is insecure Secure Insecure Client Service Transport Security In theory relay service can Eavesdrop on communication Tamper with messages In practice Impractical given volumes of traffic Microsoft is not the NSA Microsoft has the highest integrity Message Security Transfer to relay and from relay is secure Regardless of transport May require setup steps Installing service certificates in client trusted people folder Modifying client identity address tag Modifying client config file to list certificate Message Security Journey inside relay is secure Secure Insecure Client Service Message Security Relay service cannot Eavesdrop on communication Tamper with messages In practice it is vital to assure customers of end-to-end privacy and integrity I recommends message security for all relayed communication Has additional benefits as well Hybrid Local security context Message Security Message may contain client's credentials For service local authorization Integration with legacy is common scenario too Best practice is for relay service to authenticate and authorize Avoid burden service with unwanted traffic No need for client credentials on service And managing them I recommends message security without client credentials Anonymous towards service When possible Transfer Security Streamlining host with my ServiceBusHost public class ServiceBusHost : DiscoverableServiceHost { public ServiceBusHost(object singletonInstance,params Uri[] baseAddresses); public ServiceBusHost(Type serviceType,params Uri[] baseAddresses); public void ConfigureAnonymousMessageSecurity(); public void ConfigureAnonymousMessageSecurity(string serviceCert); public void ConfigureAnonymousMessageSecurity(string serviceCert, StoreLocation location,StoreName storeName); public void ConfigureAnonymousMessageSecurity(StoreLocation location, StoreName storeName,X509FindType findType,object findValue); //More members } Transfer Security No other setting in config or code Requires certificate for message security Unspecified Reads from config file If cert specified is empty string Will use service namespace for certificate name Defaults location to LocalMachine and My store Will use RM as well ServiceBusHost host = new ServiceBusHost(typeof(MyService)); host.ConfigureAnonymousMessageSecurity("MyServiceCert"); host.Open(); Transfer Security Can use my declarative security public enum ServiceSecurity { None, Anonymous, BusinessToBusiness, Internet, Intranet, ServiceBus } [SecurityBehavior(ServiceSecurity.ServiceBus)] class MyService : IMyContract {...} ServiceHost host = new ServiceHost(typeof(MyService)); host.Open(); Transfer Security Streamline client with my ServiceBusClientBase<T> Defaults to anonymous message security with service namespace as cert Can specify username creds as well public abstract class ServiceBusClientBase<T> : ClientBase<T> where T : class { public ServiceBusClientBase(); public ServiceBusClientBase(string endpointName); public ServiceBusClientBase(Binding binding,EndpointAddress remoteAddress); public ServiceBusClientBase(string username,string password); ... protected virtual void ConfigureForServiceBus(); protected virtual void ConfigureForServiceBus(string username,string password); } Transfer Security [ServiceContract] interface IMyContract { [OperationContract] void MyMethod(); } class MyContractClient : ServiceBusClientBase<IMyContract>,IMyContract { public MyContractClient() {} public void MyMethod() { Channel.MyMethod(); } } Resources Programming WCF Services 3rd Edition Juval Löwy, O'Reilly 2010 www.idesign.net Code library Coding standard Sample architecture report IDesign Method™ Architect’s Master Class November 2011 http://www.idesign.net/idesign/download/IDesignCD.zip More at TechEd Discover a New WCF with Discovery Monday 3:00 PM The Architect Tuesday 8:30 AM http://northamerica.msteched.com www.microsoft.com/teched www.microsoft.com/learning http://microsoft.com/technet http://microsoft.com/msdn