SQL Server Service Broker
SQL Server Service Broker
15
SQL Server Service Broker
Messaging Applications
Messaging applications are nothing new. Almost all large scalable enter-
prise applications use some sort of messaging infrastructure. Messaging
applications take a different approach to providing a service than applica-
tion based on functions. When you need a service from a message-based
application, you send it a message and go on about your business. If you
care, the service will some time later return to you a message concerning
the status or completion of your request. Some of the compelling reasons
for message-based applications are the following.
503
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 504
person who enters the trade and the one who completes the process-
ing of the trade by actually selling or buying the stock are different
people separated in time. Service Broker can manage the trade from
the time it is initially entered until it is completed at some later time
so that an application can concentrate on implementing each phase
of the processing of a trade. Service Broker is completely capable of
managing deferred processing over indefinite spans of time, even
months or years, and across database restarts.
• Distributed processing—The work associated with a task must be
completed in a timely manner. However, it is often quite difficult to
predict in advance how many tasks there will be and how many
resources it will take to complete them. Distributed systems allow
processing resources to be applied where there are needed and be
incrementally expanded without changing the applications that
make use of them. Service Broker allows system administrators to
manage the resources in a distributed system so that the application
can be developed as though all the resources it needs were always
available to it.
Applications Service
Service Program
Or Order Program
de
r msg
Queue
msg
msg
msg
msg
Order
r
de Order Program
Or
Managed by msg
Service Broker
msg
msg
Messages
Invoking of
accumulate
service program
in queue
disabled Instances of service
program increased
application. This, then, is the second main feature of Service Broker: It pro-
vides a complete framework based on SQL Server for implementing reliable
messaging applications.
It is hard to overemphasize the importance of this feature—without
Service Broker or some similar framework, probably 80% of the code writ-
ten for a messaging application would be for infrastructure, not the prob-
lem space of the application. The resulting framework would, of course,
not be SQL Server and would require a completely different set of skills
and utilities than those needed to maintain SQL Server. A Service Broker
application is just a collection of SQL Server objects that can be maintained
using the same skills and tools used to maintain anything else in SQL
Server.
A note on terminology: Terms that refer to type and instance are often
overloaded and depend on context, which is often not clear, to distinguish
between which overload is being used. The term “message” might some-
times refer to the definition of the format of a message and at other times
refer to an actual message. Anytime the term “message” is used in this
chapter, it is referring to an actual message that complies with a message
type definition; that is, it is an instance of some message type. The term
“message type” will always be used to refer to a definition of a message
format. This distinction is necessary because Service Broker not only man-
ages messages, it also manages message types.
A service uses Service Broker to send a message to another service. Ser-
vice Broker does this by putting the message into an output queue and
then sending it, possibly at a later time, to the queue for the other service.
A number of messages may build up in the output queue, waiting to be
sent, but they will eventually be sent and sent in the order in which they
were put into the queue.
The advantage of this extra layer of queuing is that the service sending
the message never waits for anything. But the extra layer also introduces
extra overhead. Service Broker will skip the output queue when both ser-
vices are on the same instance of SQL Server. In this case, it will put the
message directly into the queue from which the receiving service gets its
messages. Figure 15-3 shows how Service Broker efficiently sends a mes-
sage from one service to another.
So far we have seen a very general picture of how Service Broker is
used to make messaging applications. Service Broker is a framework and
extensions to T-SQL that are used to create and use the components used to
build a message-based application. We have already used some of these
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 509
Service
msg
msg
msg
msg
Different SQL Server Instances
Service
msg
msg
Same SQL Server Instance
Figure 15-3: Sending Messages between Services
Queue Queue
status
status
done
fulfill
fulfill
status status
later. Here we just want to present a model of how Service Broker uses
conversations.
A conversation is created using BEGIN DIALOG CONVERSATION. It must
list a FROM service and a TO service by name; a conversation always takes
place between two services. The FROM service is, in effect, the reply-to
address the TO service will use when it needs to reply to a message. Mes-
sages for the FROM service will go into the queue specified by the FROM ser-
vice, and similarly for the TO service.
A conversation must also specify the contract from the set of contracts
listed as being supported by the TO service. This limits the message types
used in the conversation to those listed in the contract. It further limits the
FROM service, in this conversation, to receiving in its queue only those mes-
sage types marked as being sent by an INITIATOR or ANY. And likewise it
limits the TO service to receiving in its queue only those message types
marked as being sent by a TARGET or ANY.
Although you can think of “sending a message to a service,” there is no
way to do this directly. In fact, probably the biggest hurdle in understand-
ing Service Broker is in understanding how it manages conversations.
Messages are not sent to a service, they are sent ON a particular conversa-
tion. This is how messages are correlated and ordered; obviously, all mes-
sages sent ON the same conversation are correlated.
A service program reads a message from a queue by using the T-SQL
command RECEIVE. A queue physically is a table, and in effect this selects a
row from the table and then deletes it. The message includes a conversation
handle, which identifies the conversation on which the message was sent.
If the service program needs to send a reply to that message, it sends
the reply message on the conversation handle that was in the message it
read out of the queue. This way, the service program does not need to spec-
ify the specific service as the recipient of the message. Sending messages
on a conversation handle rather than to a specific service greatly simplifies
creating distributed applications.
Hopefully, by this point you will be thinking, “OK, I can easily reply to
any message I receive; how does the first message that starts things going
get into the queue?” When a program, which could be a stored procedure
or any program that has access to SQL Server, creates a conversation using
BEGIN DIALOG CONVERSATION, it gets back a conversation handle. It can
then send a message on that conversation handle, and the message will be
sent to the queue of the TO service. From there what happens depends on
the service program that reads that message out of that queue.
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 513
with the message will be locked. Messages are read from a queue using the
RECEIVE command and sent to a queue using the SEND command.
The second way to lock a conversation group is to use the GET
CONVERSATION GROUP command. The GET CONVERSATION GROUP command is
issued for a particular queue. The command locks the conversation group
associated with the first message in the queue from a conversation
group that is not locked. Note the logic here: The GET CONVERSATION GROUP
command will skip over messages from conversation groups that are
locked until it finds one from a conversation group that is not locked.
The lock associated with a conversation group has a lifetime. It remains
locked until the transaction under which it was locked completes. In typ-
ical usage, a transaction will have been started before RECEIVE or GET
CONVERSATION GROUP is called.
When a conversation group is locked, all threads except for the one
that locked the conversation group are blocked when they try to use
RECEIVE to get a message that is from the locked conversation group.
A RECEIVE that attempts to get a message from a different conversation
group that is not locked will not be blocked.
The RECEIVE command can be selective about which messages is will
read from a queue. It can choose to receive all messages in a queue, only
the messages associated with a particular conversation group, or the mes-
sages for a particular dialog.
In typical usage, a transaction is started, GET CONVERSATION GROUP is
used, and then RECEIVE is used, followed by either a COMMIT TRANSACTION
or a ROLLBACK TRANSACTION. If a ROLLBACK TRANSACTION is used, all mes-
sages that were read from the queue are placed back into it, and all the
messages that were sent are removed from the queues that received them.
Until the transaction is committed, of course, nothing that was sent is vis-
ible to the outside world.
Though it is not required, RECEIVE is typically used after a GET
CONVERSATION GROUP command. Using GET CONVERSATION GROUP first will
find a message from an unlocked conversation group, lock the conver-
sation group, and then return the conversation group ID. The RECEIVE can
then be used to get messages only for the conversation group that GET
CONVERSATION GROUP locked, and thus is guaranteed not to block. If
RECEIVE is used to indiscriminately read messages from a queue, it may
become blocked if one of the messages it is trying to read from the queue is
from a locked conversation group. Locking on a RECEIVE command can
lead to decreased scalability and performance.
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 515
the service process starts working on one of the line items. As luck would
have it, the instance of the service process working on the line item finishes
first and tries to insert the line item into the line item table, which fails
because it violates referential integrity.
It turns out in the end, the work order would be properly inserted into
the database because queues are transactional, and when the insert failed,
the message would be put back into the queue and processed again later
after the header had been inserted, but at the cost of a lot of overhead.
Now let’s look at what happens with conversation group locks. The
messages for the header and all the line items are in conversations that are
in the same conversation group. The application always puts the message
with the header into a queue first, followed by a message for each line
item. One of the instances of the service process uses GET CONVERSATION
GROUP, which, as it happens, locks the conversation group for a work order.
It then uses RECEIVE to get all the messages in the queue associated with
the conversation group it has just locked. This could include the header
and a number of line items. It processes these in order by making INSERTs
into the appropriate tables and then returns.
It may be that a second instance of the service process, running at the
same time, also does a GET CONVERSATION GROUP. It may also lock a con-
versation group for a work order, but it will not be the same work order as
the first instance locked. It will proceed to process this second work order
in the same way the first instance is processing the first work order.
After the first instance of the conversation group finishes, it releases the
lock on the conversation group. Then yet another instance of the service
process does a GET CONVERSATION GROUP. It may end up locking the con-
versation group associated with the first work order and process subse-
quent line items that were put into queue after the first instance of the
service processed completed.
There are many variations on this theme. For example, the service
process might issue a second RECEIVE after it is done processing the first
set of messages but before it completes the transaction, to see if any more
messages have arrived for the conversation group it has locked.
There are two important things to note about this example. One is that
every possible instance of a service process is running at the same time,
each working on a different work order and doing so without ever block-
ing the others. The second is that a queue can continue receiving more
messages for a conversation group even while that conversation group is
locked. Neither of these things would be possible if a service process used
the BEGIN TRANSACTION, SELECT, DELETE sequence to remove messages
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 517
from a queue. Conversation groups and their locks are crucial to the effi-
cient operation of Service Broker.
Another important use of a conversation group is to have a way to
maintain state across the conversations in a conversation group. There is
always state to be maintained in a messaging application. A trivial example
of this is a messaging application that is used to process a purchase order
and has to keep track of the purchase order number. It can do this in three
ways. One is to keep track of the purchase order number in memory, like a
local variable. The second is to put the purchase order number in every
message. And the last is to put the purchase order number in a table in SQL
Server. Of course, if the state involved was a just purchase order number,
almost any solution would work, but in real applications there is a lot more
state than that.
The first option is not scalable and is very hard to manage. The more
service programs there are, the more memory required to hold onto their
purchase order numbers. This means that the memory requirements
would be growing at a rate greater than the number of purchase orders
being processed.
The second solution is reasonably easy to manage and does use SQL
Server for storage, but it also is not scalable. The problem is that the stor-
age required for purchase order numbers goes up as the number of mes-
sages increases. This in effect multiplies the amount of storage required in
SQL Server by the number of messages involved, not just the number of
purchase orders involved.
Both of the first two solutions also suffer from the problem of data
being duplicated in many places. Of course, in practice this would be an
unreliable way to maintain state.
What you really want to do is to put all the state for a conversation
group into some tables in SQL Server. That way, there is only one copy of
the state and just one place to maintain it. For this to work, however, you
will need two things. Both are easy to get. First of all you need something
to key the state you will be storing in SQL Server. The conversation group
ID is unique and is a UNIQUEIDENTIFIER, so it is ideal to use for a key.
The second thing you need is a lock, and you have that too. As long as
your service program is accessing a queue under a transaction, you can be
sure other service programs are not touching the shared state. In fact, this
is another use of the GET CONVERSATION GROUP command. Using RECEIVE
locks the conversation group, but it also reads the queue. Sometimes you
need to access or manipulate the state you are sharing within a conversa-
tion group before you read the queue. GET CONVERSATION GROUP gets the
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 518
conversation group associated with the next message in the queue and
returns the conversation group ID, but it does not read the queue. In either
case, you can then use the conversation group ID to look up the state and
then decide whether or not the queue should be read.
Message Type
It is of vital importance that the sender and the receiver in a messag-
ing application understand what messages will be sent. In Service Broker
the description of the messages is defined in a message type object. The
message type object defines the name of the message and the type of data
the message contains. For each database that participates in a conversa-
tion, an identical message type is created.
Listing 15-1 shows the syntax for creating a message type.
A broker must request that a brokerage make a trade, and that broker-
age must acknowledge that request. Two messages will be required to
do this:
Listing 15-2 shows the two message types created in order to accom-
plish the order entry. Both use XML encoding, which means that any a
message with valid XML will be processed.
In Listing 15-2 the various endpoints that receive the messages with the
TradeEntry and TradeAck message type don’t care about the XML as
such. They try to process it as long as it is well-formed XML. This may not
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 521
Listing 15-3: Example of Creating Message Types Whose Messages Will Be Validated
against XML Schemas
—create the schema collection for the tradeEntry message
CREATE XML SCHEMA COLLECTION TradeEntrySchema AS
N’<?xml version=”1.0” ?>
<xsd:schema xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
targetNamespace=
“http://www.develop.com/DMBrokerage/schemas/tradeEntry”>
<xsd:complexType name=”tradeEntry”>
<xsd:sequence>
<xsd:element name=”RICCODE” type=”xsd:string”/>
<xsd:element name=”CustomerID” type=”xsd:int”/>
<xsd:element name=”OrderID” type=”xsd:int”/>
<xsd:element name=”Date” type=”xsd:date”/>
<xsd:element name=”BuySell” type=”xsd:date”/>
<xsd:element name=”Volume” type=”xsd:int”/>
<xsd:element name=”Price” type=”xsd:decimal”/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>’
<xsd:complexType name=”tradeAck”>
<xsd:sequence>
<xsd:element name=”OrderID” type=”xsd:int”/>
<xsd:element name=”AckId” type=”xsd:int”/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>’
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 522
• http://schemas.microsoft.com/SQL/ServiceBroker/
DialogTimer—A dialog can have an explicit timer assigned.
This message is received when the timer expires.
• http://schemas.microsoft.com/SQL/ServiceBroker/Error—
Service Broker creates error messages based on this message type to
report errors to the application. This message type can also be used
by the application to report errors or violation of business rules.
• http://schemas.microsoft.com/SQL/ServiceBroker/
EndDialog—When a dialog ends, the broker sends the EndDialog
message to the remote endpoint.
These message types are implicitly part of every contract, so any target
can receive instances of them.
CONTRACTS 523
Contracts
Service Broker services need to know what messages to expect, the outline
of the messages, and what messages they can send. As we saw earlier, a
message type defines the message, and we use a contract to define what
messages each service (endpoint) can send and receive. Contracts are cre-
ated and persisted in each database that participates in a conversation.
As we will cover later, the endpoints can be defined to be either the ini-
tiator or the target of a conversation. Subsequently, message types can be
defined by the contract to be sent either by the initiator, the target, or
both. Listing 15-5 shows the syntax to create a contract.
Queues
The queue is used to store the messages the endpoints send. When the ser-
vice at one end sends a message to the service at the other end, the message
is placed in a queue at the receiving end. Later, when the application
receives the message and commits the transaction, the broker deletes the
messages from the queue. The service broker manages the queues and pre-
sents a database table-like view of the queues.
The syntax to create a queue is shown in Listing 15-7.
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 525
QUEUE S 525
There are quite a few options when you are creating a queue, and a
short explanation of the various arguments follows. The queue is the only
Service Broker object that can be named with a three-part name.
RETURN 0
GO
RETURN 0
QUEUE S 527
PROCEDURE_NAME = tradeAckProc,
MAX_QUEUE_READERS = 5,
EXECUTE AS SELF)
The queues created in Listing 15-8 act as receive queues for replies and
error messages.
When you create a queue, you create an object of the type Service
Queue. This object maps to a SQL Server internal table with the same name
as the queue. To view what queues exists in a database, you can do a
SELECT against the sys.service_queues catalog view. You can view the
content of a queue through a simple SELECT statement: SELECT * FROM
queue_name. Issuing a SELECT against one of the created queues in Listing
15-8 results in an empty resultset, but at least you can see the columns.
Table 15-1 shows the content of a queue.
In the process of developing a Service Broker application, we now have
the “basic plumbing,” which consists of the following:
• Message types
• Contracts
• Queues
SERVICE S 529
Services
A service is an endpoint for specific functionality in the Service Broker
application. Based on the service name, Service Broker routes messages
between databases and puts the messages on the queue for that particular
service and message type. The specific functionality that the service is an
endpoint for is defined by the contract. By specifying the contract, the ser-
vice indicates it serves as a target for that particular functionality.
Having said this, we can see that a service:
The syntax specifies both a queue name and a contract name. By defin-
ing those arguments, we make sure that any message(s) based on the
defined contract(s) are delivered to that particular queue.
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 530
In the stock trading application, we now have two message types, one
contract, and one queue in each participating database. Listing 15-10 shows
the code to tie this together. The code creates a service in each database that
maps to a queue and a contract.
CREATE SERVICE
[//www.develop.com/DMBrokerage/TradeEntryService]
ON QUEUE tradeEntryQueue
([//www.develop.com/DMBrokerage/EnterTrade])
Message Type:
TradeEntry
VALIDATION = WELL_FORMED_XML
Contract
EnterTrade Service: Queue:
TradeEntry tradeEntryQueue
Messages:
TradeEntry
TradeAck
DIALOGS 531
Dialogs
Service Broker applications communicate through conversations. A conversa-
tion involves endpoints communicating with each other. Theoretically, the
conversation can be one-to-one, one-to-many, or even many-to-many. How-
ever, at this time Service Broker only supports one-to-one conversation. This
type of conversation is called a dialog. A dialog is communication between
exactly two endpoints. It is a logical connection between service programs,
which run on service brokers. The dialog ensures that any messages associ-
ated with a dialog are delivered exactly once and in the order in which they
were sent throughout the lifetime of the dialog. This is an extremely impor-
tant point because the lifetime of the dialog can span several transactions.
Other messaging applications guarantee in-order delivery within a trans-
action but not spanning multiple transactions.
The dialog ensures in-order delivery by sequence numbering of the
messages. The sending endpoint assigns a sequence number to the mes-
sage. This sequence number is used by the receiving endpoint to order the
messages correctly. If a message is received out of order, Service Broker
holds on to the message until the missing messages have arrived. At that
time, the out-of-order message is put on the queue.
In a messaging application, it may be of importance to correlate mes-
sages from endpoints if there are multiple dialogs. In other words, you
want to tell which received messages correspond to which sent messages.
The dialog handles correlation automatically for you. The correlation
of messages is handled by a unique identifier of the conversation: the
conversation_handle.
Another important part of dialogs is the message acknowledgment.
Dialogs incorporate automatic message acknowledgment for all messages
with a sequence number. When a message is sent, the sending broker keeps
the message on the transmission queue until an acknowledgment has been
received from the remote broker.
Before an application starts sending messages, it needs to establish a
conversation. It creates a dialog. At this time it needs to indicate what end-
points are involved and what contract to use. Therefore, the syntax to start
communication is shown in Listing 15-11.
[ WITH
[ { RELATED_CONVERSATION = conversation_handle
| RELATED_CONVERSATION_GROUP = conversation_group_id } ]
[ [ , ] LIFETIME = dialog_lifetime ]
[ [ , ] ENCRYPTION = { ON | OFF } ] ]
DIALOGS 533
SELECT service_broker_guid
FROM sys.databases
WHERE database_id = db_id(‘<db_name>’)
The code in Listing 15-12 is run from the broker/trader database and
causes an entry in the conversation endpoints table to be made. This can
be investigated by calling SELECT * FROM sys.conversation_endpoints.
At this stage, nothing has happened in the brokerage database yet. Noth-
ing will happen until we send a message. This can be verified by running
SELECT far_broker_instance FROM sys.conversation_endpoints in
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 534
DIALOGS 535
Conversation Group
You use the conversation group to group related conversations together.
Imagine that when an order is entered in our application, we need to do
more things than just notify the brokerage. We may have to check the
client’s credit, check against some authority that the client actually is al-
lowed to trade, and so on. In this scenario, we would probably start several
different dialogs. These dialogs would get different conversation handles,
and it might be hard for us to keep track of the different conversations. For-
tunately, Service Broker comes to help. When we start a new dialog, it
creates a new conversation group identifier automatically. The identifier
is a GUID (SQL server data type UNIQUEIDENTIFIER). The identifier is
appended to the messages we receive. You can create the identifier your-
self (in T-SQL you use NEWID()). Subsequently, when you do BEGIN DIALOG
CONVERSATION, you relate the dialog to the identifier using the syntax in
Listing 15-11. Relating the dialog to a conversation group identifier is the
solution if you want to use an existing identifier for your dialog or associ-
ate your dialog with the conversation group of an existing dialog.
To obtain the identifier within a conversation, you do a SELECT against
the conversation_group_id column in the message queue. The identifier
can also be retrieved by the GET CONVERSATION GROUP call. Calling GET
CONVERSATION GROUP gets the conversation group identifier for the next
message to be retrieved. As we will see later, the conversation group iden-
tifier is useful if we want to keep state information. In addition, it puts a
lock on the instance. See the SQL Server Books Online for the full syntax
for GET CONVERSATION GROUP.
The biggest benefit of the conversation group is that of locking the
dialogs. You may ask why it is important to lock dialogs. We have already
stated that Service Broker guarantees the in-order delivery of messages.
That is true, but the issue is that a queue can have multiple readers (this is
discussed more in the Activation section later in the chapter). In other
words, we have multithreaded queue readers.
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 536
Service Programs
Figure 15-5 illustrates the process for a new trade in our application.
These steps are done through service programs, the part of the applica-
tion that processes messages for the application. A service program is typi-
cally a stored procedure, which is activated when a message arrives on a
queue. In this case, we say the service program is the target. So, if a service
program acts as a target, then we also need something that starts a message
exchange. Therefore, a service program can be an initiator, which sends the
first message to a target. A service program can also be the initiator of one
dialog and the target of another. A service program could theoretically also
be the initiator and the target of the same dialog. This could be used when
you’re doing some time-critical processing and you may want to queue up
some work to do later when you have time.
In our stock trading application, the initiating service program is the
stored procedure that is invoked when a user places an order. The target is
the stored procedure on the tradeEntryQueue in the DMBrokerage data-
base. There is an additional target in our application. It is the stored proce-
dure in the Trader database that accepts the acknowledgments of the
trades on the tradeAckQueue.
We mentioned earlier that a stored procedure is activated when a mes-
sage arrives on a queue. In the following section, we will look a little more
closely at the activation features in Service Broker.
Activation
In a traditional messaging application, you have basically two options to
find out that a message has arrived on a queue.
Service Broker differs in some respect from this—not so much for the
polling scenario, because we can poll a queue through either T-SQL or
some external program. However, for events, it looks different.
When you rely on events, you normally need to have a program run-
ning that is listening for events. In Service Broker you do not need to do
this. Service Broker introduces an activation mechanism.
The activation in Service Broker is based on the CREATE queue syn-
tax. Remember from Listing 15-7 how the syntax takes some optional
ACTIVATION arguments. The interesting ones are PROCEDURE_NAME and
MAX_QUEUE_READERS. As we mentioned in the section about queues, the
PROCEDURE_NAME argument defines which stored procedure to activate
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 538
• Make sure the program reads messages from the correct queue. If it
doesn’t, the program will be killed and the messages will queue up.
• The activation monitor code has no way of knowing when a pro-
gram has finished executing, apart from noticing that the program
has terminated. Therefore, make sure the program exits soon after
the queue is empty.
• There may be several message types on any given queue. Make sure
the program can handle all message types.
In the last bullet item, we said that the service program should be able
to handle all message types. You may ask what all message types are. “All
message types” means the following:
1. Create two databases (it can be done in one, but it is more realistic in
two). Name them, for example, Trader1 and DMBrokerage.
2. Create the message types and contracts from Listings 15-2 and 15-6
in both databases.
3. Create the tradeAckQueue in Trader1 and the tradeEntryQueue in
DMBrokerage according to Listing 15-8. When you create the queues,
do not set any ACTIVATION arguments just yet.
4. The last thing to do before you can test is to set up the services as
in Listing 15-10.
5. When you finish the preceding steps, you can run the code in List-
ing 15-14 from the Trader1 database.
The syntax looks almost exactly like SELECT, and both SELECT and
RECEIVE return a resultset. The difference, as we mentioned earlier, is that
RECEIVE removes the message(s) from a queue, whereas SELECT leaves
them on the queue.
The WAITFOR argument in the RECEIVE syntax indicates that the
RECEIVE operation is to wait for a message to arrive on the queue if the
queue is empty or the WHERE criteria doesn’t return a result. TIMEOUT can
only be used together with WAITFOR, and it indicates, in milliseconds, how
long to wait for a message to arrive. If WAITFOR is specified and TIMEOUT
is –1 or TIMEOUT is not specified, the wait is unlimited. If a timeout occurs,
the RECEIVE statement returns an empty result.
Because WAITFOR is optional, you may ask yourself whether you
should use it or not. In messaging applications in general, it is considered
good practice to use WAITFOR (or equivalent statements). One scenario
where you probably would not use WAITFOR is when your service program
(where your receive code is) is activated by an incoming message, and you
are certain that no other messages will be arriving within the time it takes
to process a message plus the time it takes to activate a new instance of the
stored procedure. If the volume is high, it is better use a TIMEOUT value
(fairly short). The reason is that it probably does not make sense to start
a new service program for each new message. On the other hand, if the
service program is an external application and not activated by Service
Broker, you should use a reasonably long TIMEOUT.
Since the RECEIVE command allows you to do a RECEIVE with a TOP
clause, should you consider receiving message by message or multiple
messages? In most cases, you need to process the messages on a message-
by-message basis. Bearing this in mind, if you are using T-SQL, you are
probably better off doing a RECEIVE TOP(1), especially since you cannot
create a cursor of the result from a RECEIVE. You would have to retrieve the
messages into a table variable and create the cursor over that variable. If
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 542
you receive messages into an external service program, you are better off,
from a performance perspective, receiving a resultset of multiple messages.
Listing 15-16 shows the code to receive the conversation handle and
the message body from the first message in the tradeEntryQueue as a
resultset.
When you run the code in Listing 15-16 from the DMBrokerage data-
base, it retrieves the message you sent in Listing 15-14. If you do a SELECT
against the tradeEntryQueue after the RECEIVE, no messages are there.
Notice that the body of the message is output as VARBINARY. If you want to
view it as readable text, you have to cast it to another type, like this.
Listing 15-17 shows a code snippet that uses the WAITFOR and TIMEOUT
arguments. The TIMEOUT is set to one minute (60,000 milliseconds).
WAITFOR(
—receive the first message on the queue
RECEIVE TOP(1) @dh=conversation_handle,
@msg=message_body FROM tradeEntryQueue),
TIMEOUT 60000
You can test the code in Listing 15-17 by first executing the code in the
DMBrokerage database. The status bar in SQL Server Management Studio
will say “Executing Query.” Switch over to the broker/trader database and
execute the code in Listing 15-14. Switch back to the DMBrokerage database
again, and you can see that a message has been removed from the queue.
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 543
—declare variables
DECLARE @dh UNIQUEIDENTIFIER
DECLARE @msg XML
DECLARE @ack XML
DECLARE @cg UNIQUEIDENTIFIER
WHILE (1=1)
BEGIN
— 1. start a transaction
BEGIN TRAN;
SET @cg = NULL;
1 Even though a pure initiator service doesn’t expect actual data to return, it should be pre-
pared to handle error messages and end-dialog messages. For this purpose, even a pure ini-
tiator should have an activation procedure attached to the initiator queue.
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 544
BEGIN
ROLLBACK TRANSACTION
BREAK
END;
— 4. RECEIVE
— we need the conversation handle from the send
;RECEIVE TOP(1) @dh=conversation_handle,
@msg=message_body
FROM tradeEntryQueue
WHERE conversation_group_id = @cg
The reason we compare this flow with Windows message loops is that
when the process is done, it starts all over again. Let’s look at the various
parts of the model.
back, the database changes are rolled back and the removed
messages are put back on the queue. The outgoing messages are
not sent, and we can start all over again.
• Lock conversation group—Applicable if we deal with application
state. The conversation group will under all circumstances be
locked when the RECEIVE happens.
• Retrieve state data—If applicable.
• Retrieve messages—For this application, we have decided to use a
fairly short TIMEOUT and retrieve one message per RECEIVE. We
need the conversation handle in order to send messages back to
the initiator.
• Process data—In a real-world application, we would probably
receive messages for different message types on the same queue.
We need, therefore, to check what message type we receive and
process the message accordingly. For this sample, we suggest that
you just insert the message body in some table in the database.
• Send data—In our application, we need to process the incoming
data before we can send. There is nothing that says, however, that
a send has follow a receive. It can be anywhere in the model.
• End conversation—A conversation should always be ended at one
stage. It does not need to be ended when the service program exits,
if it makes sense to keep it alive. In our example, this is the last
message for this particular task, so it makes sense to end.
• Update state—If applicable.
• Commit transaction—This is where it happens. Database updates
are committed, messages are sent and received messages are taken
off the queue.
To see this in action, you need to change the stored procedure in List-
ing 15-18 so it does something with the processed data. Then it needs to be
cataloged in the DMBrokerage database. ACTIVATION arguments need to
be added to the tradeEntryQueue with the following code snippet.
Listing 15-19: Code for the Service Program in the Broker/Trader Database
CREATE PROCEDURE tradeAckProc
AS
—declare variables
DECLARE @dh UNIQUEIDENTIFIER
DECLARE @msg VARBINARY(max)
DECLARE @cg UNIQUEIDENTIFIER
DECLARE @mt NVARCHAR(max)
WHILE (1=1)
BEGIN
BEGIN TRAN;
SET @cg = NULL;
WAITFOR (
GET CONVERSATION GROUP @cg
FROM tradeAckQueue
),
TIMEOUT 10000
IF @cg IS NULL
BEGIN
ROLLBACK TRANSACTION
BREAK
END;
COMMIT TRAN
END
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 547
ROUTE S 547
Make sure that the queue in the broker/trader database (Listing 15-8)
has its activation arguments set to use the stored procedure in Listing 15-19.
Run the code in Listing 15-14 and notice how the stored procedures were
activated and handled the messages.
At this stage, messages have been exchanged between different data-
bases in the same server instance. What about message exchange between
different instances or different machines altogether? In order to achieve
message exchange between instances and/or machines, we need to dis-
cuss routes and remote service bindings.
Routes
In the Dialogs section earlier, we mentioned how entries are added in the
sys.conversation_endpoints table when a dialog is started through
the BEGIN DIALOG CONVERSATION syntax. We also mentioned that at that
time the endpoint has not been resolved to a physical location; this hap-
pens when the first message is sent.
The way Service Broker resolves endpoints is by using routes, where a
route is an entry in a routing table (sys.routes) in a specific database
and/or in MSDB (msdb.sys.routes). When Service Broker tries to resolve an
endpoint for a message originating in the local database, it first looks in the
routing table in the local database and searches for a matching service name
and a broker instance identifier (if an identifier is included in BEGIN DIALOG
CONVERSATION). If no entry is found, Service Broker searches for a matching
service in the local instance databases and picks the first that is found. When
searching for a matching local service, Service Broker does not look through
all the different databases on the local instance. It does instead a lookup
against an in-memory mapping table. If the message Service Broker tries to
resolve the endpoint for was received from outside the service (a forwarding
scenario), Service Broker looks in the routing table in MSDB.
So, by this we can see that in order to exchange messages with remote
servers, we need to create routes. A route is created with the following
syntax.
[ , MIRROR_ADDRESS = ‘next_hop_mirror_address’ ]
[ ; ]
To apply this to our example from earlier in this section, we first need
to imagine that our application runs on different machines on the same
2 In order to run this sp_configure statement, you may need to enable advanced sp_
SECURIT Y 549
network: Trader for the broker/trader database and DMBroker for the
DMBrokerage database. The following code shows how to create the nec-
essary routes (from Trader to DMBroker and vice versa).
—create the route in the trader db on the Trader machine
USE Trader1
CREATE ROUTE TradeEntry
WITH service_name =
‘[//www.develop.com/DMBrokerage/TradeEntryService] ‘,
ADDRESS = ‘TCP://DMBroker:4022’
Security
Service Broker is designed to run enterprise applications in very secure envi-
ronments. Service Broker obviously uses regular SQL Server security to
assign permissions to users in order for the users to create and use the vari-
ous Service Broker objects. However, Service Broker also has special security
requirements because of the loosely coupled and asynchronous nature of its
applications, where the involved services may be located in different trust
domains and so on. In addition, because Service Broker supports routing of
messages, it is not guaranteed that the initiator and the target are directly
connected to each other. Because of this, Service Broker can not always use
NTLM or Kerberos security but is based on public key certificates.3
3
For an explanation of certificate usage in SQL Server, look under the Certificate topic in SQL
Server Books Online.
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 550
Transport Security
The last bullet item covers transport, and we mentioned earlier that Ser-
vice Broker by default only allows the exchange of messages between
services on the same database instance. The way Service Broker enforces
this is by having the transport protocol disabled. It is controlled by this set-
ting in the registry:
HKLM\Software\Microsoft\Microsoft SQL
Server\MSSQL.<instance_no>\SSB\TransportEnabled
SECURIT Y 551
• SSPI
• Certificate authentication
SSPI
For SSPI authentication, it is required that both instances be part of the
same domain. It is also required that the master database of each instance
have a login account for the remote SQL Server’s startup service account.
Certificate Authentication
For certificate-based authentication, a certificate holding the credentials
for each local instance is required. The certificate is created for the owner of
the master database of each instance. Listing 15-20 shows the code that the
dbo for the Trader instance runs to create the certificate. The dbo for the
DMBroker instance runs similar code to create the DMBroker certificate.
The argument c:\Certs\Trader.pvk sets the path and the name of the
private key file created, and the last argument, c:\certs\Trader.cer, is
the path and file name of the actual certificate.
When the instances in the application try to connect, they somehow
need to authenticate each other based on credentials. The local credentials
are the certificate we just created. The remote credentials are being authen-
ticated against a certificate based on the public key from the remote
instance, which means that the instances need to exchange public keys.
In our example, as Listing 15-21 shows, the Trader instance creates a
specific login and creates a certificate with this login as owner, based on the
DMBroker public key.
The FROM FILE argument is the path and name of the public key certifi-
cate that the dbo of the Trader instance has received somehow from the dbo
of the DMBroker instance. The dbo of the DMBroker instance does the same
with the public key certificate it has received from the Trader instance.
Now each master database in the two instances holds its own certificate
plus the public key certificate from the other instance. When the two
instances try to authenticate each other, the certificates will be used just
because the certificates exist in the master database. Note that the authenti-
cation mode setting needs to be set to at least support authentication
(value 2).
• Full security
• Anonymous security
• No security
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 553
SECURIT Y 553
The difference between these three types is the level of trust between
the initiator and the target of the dialog and what user the connection on
each side runs as.
In full security, both sides trust each other, and the two sides have des-
ignated user accounts in the respective databases. This is accomplished
though certificates and REMOTE SERVICE BINDINGs, both of which we cover
later.
For anonymous security, it is required that the initiating service trust
the target but not vice versa. For this to work, the initiating service needs a
REMOTE SERVICE BINDING in the database. The target database needs to
enable the guest user and give the guest user SEND rights on the service.
With no security, no certificates and no REMOTE SERVICE BINDINGs exist,
and the dialog is created with ENCRYPTION = OFF. In this case both data-
bases need to enable the guest user and grant it SEND rights on the service.
By default, messages are encrypted and dialog security is used when
you are using SSB between instances. For Service Broker dialogs and mes-
sages inside the same server instance, messages are never encrypted and
dialog security is not used.
The type of security used is based on a combination of the following
three things:
Certificates
To accomplish full security, Service Broker uses certificate authentication.
The certificate authentication for dialog security is mostly set up the same
way as for transport security mentioned previously, and the syntax for cre-
ating a certificate is the same, except that the certificate is created against a
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 554
user instead of a login. To accomplish full security, take the following steps
for certificates.
The need for the user is because in full security, operations in the re-
mote database run as that particular user. For this purpose, REMOTE SERVICE
BINDINGs are used.
Notice that a contract can be defined in the case where different con-
tracts on the same service should be used with different certificates. The
last argument, ANONYMOUS, decides whether the credentials of the local
user are transferred to the remote service or not. If it is ON, the credentials
are not transferred, and the initiating service connects as guest. In this
case, guest needs SEND rights on the remote service and CONNECT rights to
the database. The default for ANONYMOUS is OFF.
30610 15 pp. 503-556 r2ah.ps 5/21/04 1:19 PM Page 555