From the previous post, Step 1: Service Broker External Activator Service Setup, we have installed a Windows Service application for Service Broker External Activator (EA). In this article, we are going to setup SQL Server to enable Service Broker (SB).
Its sample source codes can be found at: devkimchi/Service-Broker-External-Activator
SQL Server Setup for Service Broker
Business Requirements:
We are about to trace product details changes. As soon as a product is added, updated, or deleted, those change details should be logged in a separated database and table so that the log table can be utilised for other purpose.
Firstly, we need to create two databases – SourceDB
for source that contains product details and TrackingDB
for tracking that stored log details.
Creating Databases
USE [master]
GO
CREATE DATABASE [SourceDB]
GO
CREATE DATABASE [TrackingDB]
GO
`</pre>
### Creating Tables
Once both databases are created, each database needs a table respectively – `Products` on `SourceDB` and `TrackingLogs` on `TrackingDB`.
<pre>`USE [SourceDB]
GO
CREATE TABLE [dbo].[Products] (
[ProductId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Description] [nvarchar](50) NULL,
[Price] [decimal](18, 2) NOT NULL,
CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED (
[ProductId] ASC
) WITH (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY]
) ON [PRIMARY]
GO
USE [TrackingDB]
GO
CREATE TABLE [dbo].[TrackingLogs] (
[TrackingLogId] [int] IDENTITY(1,1) NOT NULL,
[Source] [nvarchar](50) NOT NULL,
[Field] [nvarchar](50) NOT NULL,
[TrackingType] [nvarchar](8) NOT NULL,
[OldValue] [nvarchar](MAX) NULL,
[NewValue] [nvarchar](MAX) NULL,
CONSTRAINT [PK_TrackingLogs] PRIMARY KEY CLUSTERED (
[TrackingLogId] ASC
) WITH (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY]
) ON [PRIMARY]
GO
`</pre>
<a name="creating-stored-procedures"></a>
### Creating Stored Procedures
We have just created two tables. So far so good. Now, we are going to create stored procedures. They will send messages to EA or receive messages from EA.
<pre>`USE [SourceDB]
GO
CREATE PROCEDURE [usp_SendTrackingRequest]
@productId AS INT,
@trackingType AS NVARCHAR(8),
@inserted AS XML,
@deleted AS XML
AS
BEGIN
SET NOCOUNT ON;
DECLARE @data AS XML
SET @data = (SELECT
@productId AS ProductId,
@trackingType AS TrackingType,
COALESCE(@inserted, '') AS Inserted,
COALESCE(@deleted, '') AS Deleted
FOR XML PATH(''), ROOT('Changes'), ELEMENTS)
DECLARE @handle AS UNIQUEIDENTIFIER
BEGIN DIALOG CONVERSATION @handle
FROM
SERVICE [TrackingInitiatorService]
TO
SERVICE 'TrackingTargetService'
ON
CONTRACT [TrackingContract]
WITH
ENCRYPTION = OFF;
SEND
ON CONVERSATION @handle
MESSAGE TYPE [TrackingRequest] (@data)
END
GO
`</pre>
- This stored procedure is called by triggers when
INSERT
,UPDATE
orDELETE
action occurs. - The triggers passes inserted/deleted records to the stored procedure.
- The stored procedure wraps passed values in an XML format.
Then the stored procedure puts the XML value into a message and send it to SB.
There are two key parts in this stored procedure:
Both inserted and deleted values are formatted as an XML type as SB basically consumes SOAP message format.
A conversation is opened to send the XML message to an external application through SB and EA.
Make sure that the stored procedure only starts conversation and send the message through the conversation. The external application receives the message and processes it and closes the conversation.
Creating Servie Broker Objects
This is the most important part of this post to setup Service Broker. Consuming SB requires many different entities such as
Message Type
,Contract
,Queue
,Service
andEvent Notification
. Now, we are creating those objects in this order –Message Type
,Contract
,Queue
,Service
andEvent Notification
as they are all dependent on another.Enable Service Broker
As
SourceDB
is the one to track changes, we need to enable SB on this database. Make sure that this usesALTER DATABASE
statement, which means that all transactions must be completed before executing theALTER
statement. However, for some databases, this might not be possible. In this case, like below, put additional clause,WITH ROLLBACK IMMEDIATE
. By doing this, any transaction at the instance of runningALTER DATABASE
statement will be rolled back.`IF EXISTS (SELECT * FROM sys.databases WHERE name = 'SourceDB' AND is_broker_enabled = 0) BEGIN ALTER DATABASE [SourceDB] SET NEW_BROKER WITH ROLLBACK IMMEDIATE END GO `
Mesage Type
We have two message types – request and response. They define how messages are formed.
`CREATE MESSAGE TYPE [TrackingRequest] VALIDATION = WELL_FORMED_XML GO CREATE MESSAGE TYPE [TrackingResponse] VALIDATION = NONE GO `
Contract
Both message types are bunched in a contract.
`CREATE CONTRACT [TrackingContract] ( [TrackingRequest] SENT BY INITIATOR, [TrackingResponse] SENT BY TARGET ) GO `
Even though, this is not the exact simile, it is easy to understand that
INITIATOR
is a message sender andTARGET
is a message receiver.Queue
Messages are stored in queues before being consumed by services.
`CREATE QUEUE [TrackingRequestQueue] WITH STATUS = ON, RETENTION = OFF ON [PRIMARY] GO CREATE QUEUE [TrackingResponseQueue] WITH STATUS = ON, RETENTION = OFF ON [PRIMARY] GO CREATE QUEUE [TrackingNotificationQueue] WITH STATUS = ON, RETENTION = OFF ON [PRIMARY] GO `
Each queue takes responsibility for each message types. Wait. There is another queue,
TrackingNotificationQueue
, doesn’t belong to either request nor response. This queue will be consumed by EA, which will be explained later on.Service
Services manage queues which a contract combines.
`CREATE SERVICE [TrackingInitiatorService] ON QUEUE [TrackingResponseQueue] ([TrackingContract]) GO CREATE SERVICE [TrackingTargetService] ON QUEUE [TrackingRequestQueue] ([TrackingContract]) GO CREATE SERVICE [TrackingNotificationService] ON QUEUE [TrackingNotificationQueue] ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]) GO `
There are three distinctive services. One for INITIATOR (sender) and another for TARGET (receiver) and the other for EA. Please bear in mind that sender looks after the RESPONSE queue and receiver takes care of the REQUEST queue. Both request service and response service are bundled by a contract, while
TrackingNotificationService
uses default event notification contract.Event Notification
We have created all necessary message types, contracts, queues and services. Now, we need to create an event notification to throw messages to EA.
`CREATE EVENT NOTIFICATION [TrackingEventNotification] ON QUEUE [TrackingRequestQueue] FOR QUEUE_ACTIVATION TO SERVICE 'TrackingNotificationService', 'current database' GO `
This raises an event as soon as a message is loaded on the queue,
TrackingRequestQueue
, and activated. At the same time, the event lets the service,TrackingNotificationService
, know the EA starts processing the message. When the EA application receives this event notification, it looks forTrackingRequestQueue
if there is a message contracted asTrackingRequest
or not.Let’s go back to the stored procedure we created above. The stored procedure sends a message,
TrackingRequest
, which is stored inTrackingRequestQueue
. This queue is consumed byTrackingTargetService
. As stated above, theTrackingTargetService
is a receiver, so a conversation is opened within the stored procedure, then the receiver sends a signal to EA to process the message.Creating Triggers
We’ve created databases, tables, stored procedures and SB objects. Now, we are creating triggers to get what the changes are.
`CREATE TRIGGER [dbo].[trg_INS_Products] ON [dbo].[Products] AFTER INSERT AS BEGIN SET NOCOUNT ON DECLARE @productId AS INT DECLARE @trackingType AS NVARCHAR(8) DECLARE @inserted AS XML DECLARE @deleted AS XML SELECT @productId = ProductId FROM inserted SET @trackingType = 'INSERT' SET @inserted = (SELECT * FROM inserted FOR XML PATH(''), ROOT('Row'), ELEMENTS) SET @deleted = NULL EXECUTE [dbo].[usp_SendTrackingRequest] @productId, @trackingType, @inserted, @deleted END GO CREATE TRIGGER [dbo].[trg_UPD_Products] ON [dbo].[Products] AFTER UPDATE AS BEGIN SET NOCOUNT ON DECLARE @productId AS INT DECLARE @trackingType AS NVARCHAR(8) DECLARE @inserted AS XML DECLARE @deleted AS XML SELECT @productId = ProductId FROM inserted SET @trackingType = 'UPDATE' SET @inserted = (SELECT * FROM inserted FOR XML PATH(''), ROOT('Row'), ELEMENTS) SET @deleted = (SELECT * FROM deleted FOR XML PATH(''), ROOT('Row'), ELEMENTS) EXECUTE [dbo].[usp_SendTrackingRequest] @productId, @trackingType, @inserted, @deleted END GO CREATE TRIGGER [dbo].[trg_DEL_Products] ON [dbo].[Products] AFTER DELETE AS BEGIN SET NOCOUNT ON DECLARE @productId AS INT DECLARE @trackingType AS NVARCHAR(8) DECLARE @inserted AS XML DECLARE @deleted AS XML SELECT @productId = ProductId FROM deleted SET @trackingType = 'DELETE' SET @inserted = NULL SET @deleted = (SELECT * FROM deleted FOR XML PATH(''), ROOT('Row'), ELEMENTS) EXECUTE [dbo].[usp_SendTrackingRequest] @productId, @trackingType, @inserted, @deleted END GO `
For more readability, triggers for
INSERT
,UPDATE
andDELETE
are separated. They do the literally the same job – To find a record affected, wrap the record as an XML format and send the record to the stored procedure we created above.Granting Permissions
For Windows 7, Windows Server 2008 or later, you might have heard of the term, Virtual Account. This is not a real account but to work as like a service account in Windows 7, Windows Server 2008 or later which is not on Active Directory. As we have already installed Service Broker External Activator Service, we have a virtual account,
NT SERVICE\SSBExternalActivator
. This account has already been assigned to the Windows Service application.Permissions need to be granted onto two accounts –
NT SERVICE\SSBExternalActivator
andNT AUTHORITY\ANONYMOUS LOGON
. As the former is a virtual account, it actually doesn’t exist. Therefore, in order to access to database, it instead uses the latter. Keep this in your mind. Of course, for your production purpose, it would be better to create a service account.`USE [master]
GOIF NOT EXISTS (SELECT * FROM sys.syslogins WHERE name = ‘NT SERVICESSBExternalActivator’)
BEGINCREATE LOGIN [NT SERVICESSBExternalActivator] FROM WINDOWS
END
GOUSE [TrackingDB]
GOIF NOT EXISTS (SELECT * FROM sys.sysusers WHERE name = ‘NT SERVICESSBExternalActivator’)
BEGINCREATE USER [NT SERVICESSBExternalActivator] FOR LOGIN [NT SERVICESSBExternalActivator]
END
GOALTER ROLE [db_owner] ADD MEMBER [NT SERVICESSBExternalActivator]
GOUSE [SourceDB]
GOIF NOT EXISTS (SELECT * FROM sys.sysusers WHERE name = ‘NT SERVICESSBExternalActivator’)
BEGINCREATE USER [NT SERVICESSBExternalActivator] FOR LOGIN [NT SERVICESSBExternalActivator]
END
GOALTER ROLE [db_owner] ADD MEMBER [NT SERVICESSBExternalActivator]
GO– Allows CONNECT to [SourceDB].
GRANT CONNECTTO [NT SERVICESSBExternalActivator]
GO
– Allows RECEIVE from the queue for the external actvator app.
GRANT RECEIVEON [TrackingNotificationQueue] TO [NT SERVICESSBExternalActivator]
GO
– Allows VIEW DEFINITION right on the service for the external activator app.
GRANT VIEW DEFINITIONON SERVICE::[TrackingNotificationService] TO [NT SERVICESSBExternalActivator]
GO
– Allows REFRENCES right on the queue schema for the external activator app.
GRANT REFERENCESON SCHEMA::dbo TO [NT SERVICESSBExternalActivator]
GO
USE [master]
GOIF NOT EXISTS (SELECT * FROM sys.syslogins WHERE name = ‘NT AUTHORITYANONYMOUS LOGON’)
BEGINCREATE LOGIN [NT AUTHORITYANONYMOUS LOGON] FROM WINDOWS
END
USE [TrackingDB]
GOIF NOT EXISTS (SELECT * FROM sys.sysusers WHERE name = ‘NT AUTHORITYANONYMOUS LOGON’)
BEGINCREATE USER [NT AUTHORITYANONYMOUS LOGON] FOR LOGIN [NT AUTHORITYANONYMOUS LOGON]
END
GOALTER ROLE [db_owner] ADD MEMBER [NT AUTHORITYANONYMOUS LOGON]
GOUSE [SourceDB]
GOIF NOT EXISTS (SELECT * FROM sys.sysusers WHERE name = ‘NT AUTHORITYANONYMOUS LOGON’)
BEGINCREATE USER [NT AUTHORITYANONYMOUS LOGON] FOR LOGIN [NT AUTHORITYANONYMOUS LOGON]
END
GOALTER ROLE [db_owner] ADD MEMBER [NT AUTHORITYANONYMOUS LOGON]
GO– Allows CONNECT to [SourceDB].
GRANT CONNECTTO [NT AUTHORITYANONYMOUS LOGON]
GO
– Allows RECEIVE from the queue for the external actvator app.
GRANT RECEIVEON [TrackingNotificationQueue] TO [NT AUTHORITYANONYMOUS LOGON]
GO
– Allows VIEW DEFINITION right on the service for the external activator app.
GRANT VIEW DEFINITIONON SERVICE::[TrackingNotificationService] TO [NT AUTHORITYANONYMOUS LOGON]
GO
– Allows REFRENCES right on the queue schema for the external activator app.
GRANT REFERENCESON SCHEMA::dbo TO [NT AUTHORITYANONYMOUS LOGON]
GO
The scripts above give permissions to both NT SERVICE\SSBExternalActivator
and NT AUTHORITY\ANONYMOUS LOGON
to access to TrackingNotificationService
and TrackingNotificationQueue
. By granting permissions like this, the EA application can receive messages from SB.
So far, we have created service broker objects. In the next post, Step 3: External Activator Application Development, we will develop an application to consume the messages sent from SB.