Back again with Part 4 in this series. This time around we are going use BizTalk to send a message to an Azure Service Bus Topic using the new SB-Messaging Adapter.
What is the difference between a Service Bus Queue and Topic?
Conceptually they are very similar in the sense that they both provide a durable message store within Azure where Publishers can publish messages and Consumers can consume messages. Queues store messages in a First In First Out (FIFO) manner and provide a competing consumer experience. What this means is that if we have two queue clients that are polling the same queue then only one client will receive a copy of any given message.
Topics provide some additional features that really support a Publish/Subscribe (Pub/Sub) architecture. Topics allow for multiple clients to subscribe to the same Topic through subscriptions. Subscriptions also support SQL92 expressions and allow a consumer to filter messages out based upon BrokeredMessage Properties.
Scenario
In Part 3 I discussed how our Work Order Management system can notify our Major Account System when an Estimated Time of Restore is available. This allows Major Account Representatives the ability to reach out to customers to share the good/bad news about when their power will be restored.
We are going to build upon this scenario but instead of sending all messages to a Queue we are going to send it to a Azure Service Bus Topic instead. Due to the , fictional, growth of our company there are now two distinct groups responsible for Major accounts. One for the city of Edmonton and another for the city of Calgary. (Both of these cities exist within Alberta, Canada) Each queue Subscription client will now subscribe to events for their city. BizTalk will however just send messages to one Topic and let the Service Bus work out which message needs to be delivered to each client.
Modifying Client Application(s)
Calgary Application
using System.IO;using System.Runtime.Serialization;using Microsoft.ServiceBus;using Microsoft.ServiceBus.Messaging;using BrokeredMessageFromBizTalk;
namespace BrokeredMessageFromBizTalkCalgary{ class Receiver { const string TopicName = "<your_topic>"; static string ServiceNamespace = "<your_namespace>"; static string IssuerName = "<your_owner>"; static string IssuerKey = "<your_key>"; static string connectionString = String.Format("Endpoint=sb://{0}.servicebus.windows.net/;SharedSecretIssuer={1};SharedSecretValue={2}", ServiceNamespace, IssuerName, IssuerKey);
static void Main(string[] args) { TokenProvider tokenProvider = TokenProvider.CreateSharedSecretTokenProvider( Receiver.IssuerName, Receiver.IssuerKey); Uri uri = ServiceBusEnvironment.CreateServiceUri("sb", Receiver.ServiceNamespace, string.Empty); MessagingFactory messagingFactory = MessagingFactory.Create(uri, tokenProvider);
// Create a "Calgary" SQLFilter SqlFilter calgaryFilter = new SqlFilter("Address = 'Calgary'");
//Check to see if Calgary Subscription exists, if it does not then create it NamespaceManager namespaceManager = NamespaceManager.CreateFromConnectionString(Receiver.connectionString); if (!namespaceManager.SubscriptionExists(Receiver.TopicName, "Calgary")) {
//Create Calgary Subscription with our calgaryFilter namespaceManager.CreateSubscription(Receiver.TopicName, "Calgary", calgaryFilter);
} //Create Subscription Client using Peek/Lock Mode SubscriptionClient sc = messagingFactory.CreateSubscriptionClient(Receiver.TopicName , "Calgary", ReceiveMode.PeekLock); BrokeredMessage bm; while ((bm = sc.Receive(new TimeSpan(hours: 0, minutes: 0, seconds: 20))) != null) { var data = bm.GetBody<EstimatedTimeToRestore>(new DataContractSerializer(typeof(EstimatedTimeToRestore))); Console.WriteLine(String.Format("An estimated time of restore {0} has been received for {1}", data.RestoreTime, data.CustomerName)); Console.WriteLine("Brokered Message Property Address has a value of {0}", bm.Properties["Address"]); //Remove message from Topic bm.Complete(); }
} }}
Edmonton Application
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.IO;using System.Runtime.Serialization;using Microsoft.ServiceBus;using Microsoft.ServiceBus.Messaging;using BrokeredMessageFromBizTalk;
namespace BrokeredMessageFromBizTalkEdmonton{ class Receiver { const string TopicName = "<your_topic>"; static string ServiceNamespace = "<your_namespace>"; static string IssuerName = "<your_owner>"; static string IssuerKey = "<your_key>"; static string connectionString = String.Format("Endpoint=sb://{0}.servicebus.windows.net/;SharedSecretIssuer={1};SharedSecretValue={2}", ServiceNamespace, IssuerName, IssuerKey);
static void Main(string[] args) {
TokenProvider tokenProvider = TokenProvider.CreateSharedSecretTokenProvider( Receiver.IssuerName, Receiver.IssuerKey); Uri uri = ServiceBusEnvironment.CreateServiceUri("sb", Receiver.ServiceNamespace, string.Empty); MessagingFactory messagingFactory = MessagingFactory.Create(uri, tokenProvider);
// Create a "Edmonton" filtered subscription SqlFilter edmontonFilter = new SqlFilter("Address = 'Edmonton'");
//Create NamespaceManager object and see if Edmonton Subscription exists for our Topic NamespaceManager namespaceManager = NamespaceManager.CreateFromConnectionString(Receiver.connectionString); if (!namespaceManager.SubscriptionExists(Receiver.TopicName, "Edmonton")) { //If the Subscription does not exist, create it namespaceManager.CreateSubscription(Receiver.TopicName, "Edmonton", edmontonFilter);
}
//Create Subscription client and use Peek/Lock mechanism for delivery SubscriptionClient sc = messagingFactory.CreateSubscriptionClient(Receiver.TopicName, "Edmonton", ReceiveMode.PeekLock); BrokeredMessage bm; while ((bm = sc.Receive(new TimeSpan(hours: 0, minutes: 0, seconds: 20))) != null) { var data = bm.GetBody<EstimatedTimeToRestore>(new DataContractSerializer(typeof(EstimatedTimeToRestore))); Console.WriteLine(String.Format("An estimated time of restore {0} has been received for {1}", data.RestoreTime, data.CustomerName)); Console.WriteLine("Brokered Message Property Address has a value of {0}", bm.Properties["Address"]); //remove message from Topic bm.Complete(); }
Creating Topic
Much like we did in the previous blog post where we created a Service Bus Queue, we can also create a Service Bus Topic from the http://windowsazure.com portal
Note: We can also create Topics via code by using the NamespaceManager class much like we did for Subscriptions. I decided to use the portal just to demonstrate that we have a few options when creating Service Bus artifacts.
Modifying BizTalk Solution
Once again we are going to keep the BizTalk Solution changes to a minimum and will continue to use a Messaging Only scenario.
Testing our Solution
Conclusion
In this post we used existing BizTalk skills to send typed messages to an Azure Service Bus Topic. We promoted a property within BizTalk that was converted to a Brokered Message Property that can be used within Subscription Filters in order to route messages between different consumers.
Once again the experience is very seamless. I really do like the .Net API for Service Bus. I think it is intuitive and is very logical. As simple as it is to use this API it is hard to imagine anything being much simpler. But it has been done. The BizTalk team has made interfacing with the Service Bus even easier than using the API. From a BizTalk perspective the Service Bus is just another endpoint and requires some additional configuration. I do find that this aligns with some of the early goals of BizTalk.