Last month, in "How to Use Windows Azure AppFabric Service Bus Brokered Messaging," I introduced the key concepts and features of Windows Azure AppFabric Service Bus Brokered Messaging. Now it's time to get our hands dirty and see how we can program against this messaging infrastructure. In this article, I will present the basics of using the Service Bus Brokered Messaging APIs to manage messaging entities (such as queues, topics, and subscriptions) and for communications by sending and receiving messages in Azure-based applications.
A Tale of Two APIs
Service Bus Brokered Messaging offers two flavors of API. One is delivered in managed form as the Microsoft.ServiceBus.dll assembly that is a part of the AppFabric SDK 1.5, and the other API is simply available for REST-aware clients to use. Managed clients communicate with the Service Bus infrastructure using TCP and the Service Bus client protocol, and REST clients, naturally, use HTTP. One key item to note in restricted networking environments is that the Service Bus Client Protocol by default requires firewalls to allow outbound TCP port 9354.
For purposes of basic messaging (e.g., sending and receiving messages), these APIs provide identical functionality. In this article, I'll show usage of the two APIs in parallel to familiarize you with both so that you are comfortable in using the one that best fits your needs. For brevity, I will refer to the managed API that uses the Service Bus Client Protocol simply as the SBCP API or managed API and the REST interface as the REST API.
Getting Set Up
To use either API with the Service Bus, you will need to create a namespace within the Azure Management Portal. The procedure for doing this is well documented on MSDN. Once you've created the namespace, you will want to capture the issuer name (which is "owner" by default), the issuer key (which is akin to the password), and of course, the namespace name. All these values are available within the portal, by clicking on your namespace and examining the Properties pane on the right for Name and Default Key. You will use the issuer name, issuer key, and namespace name values in authenticating with the Service Bus.
All communication with the Service Bus is authenticated. How you authenticate varies depending on which API you use. If you are using the SBCP API, then you need to create a TokenProvider from the issuer name and issuer password you acquired from the portal, then pass this TokenProvider instance to any operations that require it, such as in the creation of a NamespaceManager or MessagingFactory. Figure 1 shows how to create the SharedSecretTokenProvider that is appropriate for this type of credential.
The TokenProvider effectively abstracts you from what is going on under the hood -- a request against the Access Control Service (ACS) that includes, among other things, your credentials. ACS replies with a token that needs to be included with any requests against the Service Bus. The SBCP API handles requesting and including the token, as well as renewing it, for you automatically.
Things are a little bit more manual when using REST, as you have to make a POST request against the ACS, providing the credentials as a name/value collection in the request body, then parse the token from the response, as Figure 2 shows.
With the TokenProvider instance (SBCP API) or the token string (REST API) in hand, you are now ready to start interacting with the Service Bus.
Creating Messaging Entities
Before you can start sending messages to a queue, you need to have a queue in place. The creation of messaging entities (queues, topics, and subscriptions) is performed by a NamespaceManager (within the Microsoft.ServiceBus namespace) in the SBCP API, and by constructing the appropriate PUT requests for the REST API.
Depending on the particular entity you are trying to create, different properties can be specified in the configuration, as Figure 3 shows, and it can be helpful to keep in mind the differences between the messaging entities:
- Topics do not support the system-provided dead-letter subqueue. Hence, only queues and topics can be configured to automatically dead-letter a message whose time-to-live has expired.
- Similarly, only subscriptions support filters, and so only they provide support for automatic dead-lettering of messages that have an exception when the filter is evaluated.
- Topics are not used to receive messages, whereas queues and subscriptions are. Thus topics cannot be configured with a Lock Duration (how long a peek/lock received message is held invisible), Max Delivery Count (how many times a message is received before being considered poisonous), or a Requires Session (enabling retrieval of messages grouped by a session).
- By contrast, only queues and topics can be used to send messages to them. Subscriptions, which cannot be sent messages, naturally do not have configurations for Requires Duplicate Detection (which detects and ignores duplicate messages sent in) and Duplicate Detection History Time Window (which controls the window of time for which a duplicate of a message already received is monitored).
- Queues and topics are identified by a path (which allows for labels with multiple segments, like /subpath1/subpath2/myqueue), whereas subscriptions cannot use a path and must use a simple single segment name like "mysubscription."
Creating a Queue
Creating a queue with the SBCP API is a matter of creating a NamespaceManager instance, then using the CreateQueue method on that instance, passing in either a queue path (in which case the defaults are taken for all other settings) or a QueueDescription (which enables you to specify values for the settings in Figure 3). You construct a NamespaceManager instance by building the URI to your Service Bus namespace using the ServiceBusEnvironment.CreateServiceUri static method, and instantiate your NamespaceManager using its constructor that takes the URI and the previously acquired TokenProvider. Figure 4 shows how to create a queue with a path of "MyFirstQueue" and the default settings for the QueueDescription.
Creating a queue via REST is similar, in that you perform a PUT of a QueueDescription against the URI for your new queue. You must be sure to provide the token in the Authorization header of the request. Figure 5 shows how you would accomplish this. Note that if you decode the byte-array response (using Encoding.UTF8.GetString), you will see a complete QueueDescription with all the default values filled in.
Creating a Topic
Creating a topic is almost identical to creating a queue. In the managed case, you use the CreateTopic method on the NamespaceManager instance and pass in either a path for the new topic or a TopicDescription. Here is what that would look like:
TopicDescription topicDescription = namespaceManager.CreateTopic(topicPath);
In the REST case, you build the URI using the topicPath (instead of the queuePath) and PUT a TopicDescription instead of the QueueDescription, as is shown for a queue in Figure 6, along with a default TopicDescription. (Note that the title element has the topicPath as its value.)
Creating a Subscription
A subscription can only be created with a topic in place. When using the SBCP API, you call the CreateSubscription method on a NamespaceManager instance and, in the most basic override, provide it the path to your topic and the name for the new subscription, as shown in the following example:
SubscriptionDescription mySubscription = namespaceManager.CreateSubscription(topicPath, name);
It is by using the other overloads of the CreateSubscription method that you can define rules, filters, and actions for that subscription. Observe that these items can be specified only when the subscription is created, and not added on later.
To create a subscription via the REST API, you follow the same pattern of performing a PUT, but this time you provide a SubscriptionDescription, which in the default case is as simple as the example shown in Figure 7. (Note that the name of the subscription is within the title element.)
Unlike for the managed case, when using REST you can, and must, specify rules (and their constituent filters and actions) in a separate call. This is also a PUT operation, where the payload is a RuleDescription, such as that shown in Figure 8. This creates a rule with a filter having an SQL expression condition (which is a string specified using an SQL-92 syntax, such as "PartnerName LIKE 'Mic%'") but no action (as indicated by the EmptyRuleAction type). For more information about the conditional syntax for rules and actions, see the MSDN articles "SqlFilter.SqlExpression Property" and "SqlRuleAction.SqlExpression Property."
Deleting Messaging Entities
Messaging entities can be deleted easily using both the SBCP and REST APIs. With the managed approach, it is simply a matter of creating an instance of NamespaceManager and calling DeleteQueue, DeleteSubscription, or DeleteTopic, as appropriate. Note that calling DeleteTopic deletes all subscriptions, too. In the REST case, it is as simple as performing a DELETE against the URI of the queue, topic, or subscription you want to delete, with the token specified in the Authorization header and an empty payload for the request body. To summarize, these URIs are of the form shown in Figure 9.
Figure 9: Summary of URIs for the various messaging entities
With a queue or topic in place, you are now ready to send a message. A message consists of headers, a fixed collection of built-in properties as well as custom properties you can add, and a message body.
In the SBCP API, a message (along with its headers and body) is represented by an instance of the BrokeredMessage class. You specify the value for any built-in headers (such as ContentType, Label, or MessageId) by setting properties on the BrokeredMessage instance, and you can add your own custom headers by adding name-value pairs to the Properties dictionary. The values of custom dictionary properties must be simple types (or string, Guid, Uri, DateTime, DateTimeOffset, or TimeSpan), and the custom property name must consist of only alphanumeric characters and the period character. The body of the message is serialized using a DataContractSerializer with a binary XmlDictionaryWriter, so it, too, must be serializable. Figure 10 shows how to create a message with a body consisting of the string "Hello world.", setting the built-in property MessageId, and adding a custom property called "Source."
To send this message to a queue or topic, you create QueueClient or TopicClient and call its Send method, passing it the BrokeredMessage instance. Such clients are created from a MessagingFactory instance by calling CreateQueueClient or CreateTopicClient, respectively. The MessagingFactory instance holds an open TCP connection to the Service Bus as long as it is active (e.g., not closed or disposed of), which it shares with any clients created from it. For example, to create a TopicClient and use it to send a message to the topic, you would use code similar to that in Figure 11. Note that when you are finished with a QueueClient, TopicClient, or SubscriptionClient instance you should close it, which is done by closing the MessagingFactory instance (which automatically closes the TCP connection for clients created from it).
To accomplish the equivalent action using REST, you must POST to the message's segment off of the queue or topic URI, providing the token in the authorization header and a UTF-8 encoded byte array for the body, as shown in Figure 12. Observe how the value of the custom "Source" header must be enclosed within quotes -- this is how you indicate to the Service Bus that the value is to be treated as a string.
You receive messages sent to a queue from the queue, and those sent to a topic from a subscription associated with that topic. There are two basic ways you can receive a message from a queue or subscription: peek/lock or receive-and-delete. In the peek/lock case, a message is not deleted from the queue or subscription until the receiver calls the Complete operation on the message. While peek/locked, the message remains invisible to other receivers for a period of time. In the receive-and-delete case, the message is deleted in the same atomic operation as it is returned to the receiver.
In the SBCP API, the receive mode is indicated in creation of the QueueClient or SubscriptionClient by specifying a second parameter (a ReceiveMode enum value) to the MessagingFactory instance's CreateQueueClient or CreateSubscriptionClient. Figure 13 shows examples of each.
Receiving from a queue or subscription in the REST API uses a different verb to indicate a peek/lock receive versus a receive-and-delete form of receive. To perform a receive-and-delete, you perform a DELETE against the /messages/head segment of your queue or subscription, but to perform a peek/lock receive, you perform a POST against the same segment. In both cases, the Authorization header must contain your token, and the body should be empty. To Complete a message, you must perform a DELETE against a URI that includes the message ID and lock ID of the message you want to complete. The values for these are found in the "BrokerProperties" response header to a receive operation (they are JSON encoded).
A Choice of Messaging Methods
Service Bus Brokered Messaging provides both managed and REST APIs that equivalently support functionality for message exchange. For these core scenarios, which API you choose to use is determined primarily by whether or not your client code can run managed code. What is powerful about having both APIs is that it enables you to send or receive messages using a mixture of managed and REST clients at the same time and with a fairly uniform approach.