Introdução ao Service Broker – Resumo

Uma feature do SQL Server que tive que aprender “na marra” para trabalhar em alguns projetos de SOA, mas que me permitiu construir soluções incríveis é o Service Broker, que no início pode parecer uma tentativa de reinventar a roda para quem já trabalha com SOA em plataformas de desenvolvimento (.NET, Java), mas para a vida do DBA é uma solução muito prática, flexível e estável, que pode até surpreender muitos desenvolvedores.

No blog, trabalhei esta feature em três situações:

Introdução ao Service Broker – Parte 1 – Monólogo

Introdução ao Service Broker – Parte 2 – Diálogo e Permissões

Introdução ao Service Broker – Processos assíncronos

Introdução ao Service Broker – Processos assíncronos

Depois de termos visto alguns aspectos básicos da estrutura do Service Broker (Monólogos e Diálogos), vamos entender como utilizar o Service Broker para executar processos de forma assíncrona.

A forma mais simples de executar processos de forma assíncrona no Service Broker é utilizar o evento de ativação da fila de mensagens, que seria algo próximo ao que já entendemos como triggers/gatilhos/eventos para novas mensagens inseridas na fila, só que este evento é executado em paralelo ao escopo do qual as mensagens são enviadas.

Para um exemplo simples, criamos o cenário de um monólogo:

USE [master];

CREATE DATABASE DB01;

GO

ALTER DATABASE DB01
	SET ENABLE_BROKER;

GO

USE DB01;

CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'su93rS3nh4';

GO

USE DB01;

CREATE MESSAGE TYPE Texto VALIDATION = NONE;

CREATE CONTRACT Contrato ( Texto SENT BY INITIATOR );

CREATE QUEUE Fila WITH STATUS = ON;

CREATE SERVICE Servico ON QUEUE Fila(Contrato);

GO

E uma procedure que será executada de forma assíncrona como gatilho de nova mensagem na fila do Service Broker, esta simplesmente fará a leitura das mensagens na fila e inserirá informações sobre estas mensagens em uma tabela:

CREATE TABLE Resultados (Id UNIQUEIDENTIFIER, Texto VARCHAR(30));

GO

CREATE PROC USP_Ativacao AS
BEGIN
	DECLARE @Mensagem VARCHAR(30)
	, @MensagemId UNIQUEIDENTIFIER
	, @MensagemTipo NVARCHAR(256);

	WAITFOR (
		RECEIVE TOP(1)
			@Mensagem = [message_body],
			@MensagemId = [conversation_handle],
			@MensagemTipo = [message_type_name]
		FROM Fila
	), TIMEOUT 2000 -- 2000ms ou 2s

	IF @MensagemId IS NOT NULL
		AND @MensagemTipo = 'Texto'
	BEGIN
		WAITFOR DELAY '00:00:01';

		INSERT INTO Resultados
		SELECT @MensagemId, 'Ativação: ' + @Mensagem;

		END CONVERSATION @MensagemId;
	END;
END;

GO

Associamos a execução da procedure ao evento de ativação da fila:

ALTER QUEUE Fila
WITH ACTIVATION (
	STATUS = ON,
	PROCEDURE_NAME = USP_Ativacao,
	MAX_QUEUE_READERS = 1,
	EXECUTE AS OWNER
)

GO

E para demonstração desta estrutura, iniciaremos o monólogo seguido da verificação dos registros de nossa tabela, neste momento ela estará vazia, e após um pequeno delay uma nova verificação da tabela, agora com os registros inseridos de forma assíncrona pela procedure da ativação da fila:

DECLARE @Mensagem VARCHAR(30)
, @MensagemId UNIQUEIDENTIFIER;

SET @Mensagem = 'Mensagem';

BEGIN DIALOG CONVERSATION @MensagemId
	FROM SERVICE Servico
	TO SERVICE 'Servico'
	ON CONTRACT Contrato;

SEND ON CONVERSATION @MensagemId
	MESSAGE TYPE Texto (@Mensagem);

-- Primeira verificação: Vazia
SELECT * FROM Resultados;

WAITFOR DELAY '00:00:05';

-- Segunda verificação: Dados inseridos
SELECT * FROM Resultados;

GO

Por fim, podemos desativar a fila e sua ativação, e excluir o banco de dados utilizado para a demonstração:

ALTER QUEUE Fila WITH STATUS = OFF, ACTIVATION ( STATUS = OFF );

GO

USE [master];

DROP DATABASE DB01;

GO

Introdução ao Service Broker – Parte 2 – Diálogo e Permissões

Caminhando com o segundo artigo sobre Service Broker, entenderemos como criar um diálogo entre dois bancos de dados na mesma instância do SQL Server. Ao contrário do monólogo, que foi apresentado no artigo anterior, os diálogos que requerem a interação entre dois bancos precisam, além das configurações padrões do Service Broker, também da opção “TRUSTWORTHY” habilitada:

USE [master];

CREATE DATABASE DB01;

ALTER DATABASE DB01
	SET ENABLE_BROKER;

ALTER DATABASE DB01
	SET TRUSTWORTHY ON;

CREATE DATABASE DB02;

ALTER DATABASE DB02
	SET ENABLE_BROKER;

ALTER DATABASE DB02
	SET TRUSTWORTHY ON;

GO

USE DB01;

CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'su93rS3nh4?db01';

USE DB02;

CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'su93rS3nh4?db02';

GO

Para que os serviços que realizarão este diálogo possam entender as mensagens enviadas e recebidas, assim como os contratos estabelecidos, é necessário que haja nos dois bancos de dados a mesma definição de contratos e tipos de mensagens.

USE DB01;

CREATE MESSAGE TYPE Requisicao
	VALIDATION = WELL_FORMED_XML;

CREATE MESSAGE TYPE Resposta
	VALIDATION = WELL_FORMED_XML;

CREATE CONTRACT Contrato (
	Requisicao SENT BY INITIATOR,
	Resposta SENT BY TARGET
);

USE DB02;

CREATE MESSAGE TYPE Requisicao
	VALIDATION = WELL_FORMED_XML;

CREATE MESSAGE TYPE Resposta
	VALIDATION = WELL_FORMED_XML;

CREATE CONTRACT Contrato (
	Requisicao SENT BY INITIATOR,
	Resposta SENT BY TARGET
);

Por fim, estabelecemos uma fila e serviço para requisições no banco de dados que receberá a interação, e também uma fila e serviço para respostas no banco de dados que inicia a interação:

USE DB02;

CREATE QUEUE FilaRequisicao
	WITH STATUS = ON;

CREATE SERVICE ServicoRequisicao
	ON QUEUE FilaRequisicao (Contrato);

USE DB01;

CREATE QUEUE FilaResposta
	WITH STATUS = ON;

CREATE SERVICE ServicoResposta
	ON QUEUE FilaResposta (Contrato);

GO

Terminada a estrutura para o diálogo, iniciamos a interação a partir do serviço de resposta para com o serviço de requisição:

USE DB01;

DECLARE @Mensagem XML = 'Requisição'
, @MensagemId UNIQUEIDENTIFIER;

BEGIN DIALOG CONVERSATION @MensagemId
	FROM SERVICE ServicoResposta
	TO SERVICE 'ServicoRequisicao'
	ON CONTRACT Contrato;

SEND ON CONVERSATION @MensagemId
	MESSAGE TYPE Requisicao(@Mensagem);

Recebemos a mensagem da fila de requisições e enviamos uma mensagem de respostas:

USE DB02;

DECLARE @Mensagem XML
, @MensagemId UNIQUEIDENTIFIER;

RECEIVE TOP(1)
	@Mensagem = [message_body],
	@MensagemId = [conversation_handle]
FROM FilaRequisicao;

SELECT @MensagemId, @Mensagem;

IF @MensagemId IS NOT NULL
BEGIN
	SET @Mensagem = 'Resposta';

	SEND ON CONVERSATION @MensagemId
		MESSAGE TYPE Resposta(@Mensagem);
END

Por fim, recuperamos da fila de respostas a mensagem, concluindo a comunicação:

USE DB01;

DECLARE @Mensagem XML
, @MensagemId UNIQUEIDENTIFIER;

RECEIVE TOP(1)
	@Mensagem = [message_body],
	@MensagemId = [conversation_handle]
FROM FilaResposta;

SELECT @MensagemId, @Mensagem;

IF @MensagemId IS NOT NULL
	END CONVERSATION @MensagemId;

Permissões

Em relação os usuários que possuem acessos limitados, mas que precisam realizar as interações, só será necessário que eles tenham a permissão de CONTROL das filas dos seus respectivos bancos de dados, por exemplo:

-- Usuário que poderá ser utilizado para interações dos serviços relacionados à fila de resposta.
USE DB01;

CREATE LOGIN Maria WITH PASSWORD = 'su93rS3nh4';

CREATE USER Maria FOR LOGIN Maria;

GRANT CONTROL ON dbo.FilaResposta TO Maria;

GO

-- Usuário que poderá ser utilizado para interações dos serviços relacionados à fila de requisição.
USE DB02;

CREATE LOGIN Heitor WITH PASSWORD = 'su93rS3nh4';

CREATE USER Heitor FOR LOGIN Heitor;

GRANT CONTROL ON dbo.FilaRequisicao TO Heitor;

GO

Por fim, uma interação completa utilizando somente da permissão de CONTROL destes usuários:

USE DB01;

EXEC AS LOGIN = 'Maria';

USE DB01;

DECLARE @Mensagem XML = 'Requisição'
, @MensagemId UNIQUEIDENTIFIER;

BEGIN DIALOG CONVERSATION @MensagemId
	FROM SERVICE ServicoResposta
	TO SERVICE 'ServicoRequisicao'
	ON CONTRACT Contrato;

SEND ON CONVERSATION @MensagemId
	MESSAGE TYPE Requisicao(@Mensagem);

REVERT;

GO

USE DB02;

EXEC AS LOGIN = 'Heitor';

USE DB02;

DECLARE @Mensagem XML
, @MensagemId UNIQUEIDENTIFIER;

RECEIVE TOP(1)
	@Mensagem = [message_body],
	@MensagemId = [conversation_handle]
FROM FilaRequisicao;

SELECT @MensagemId, @Mensagem;

IF @MensagemId IS NOT NULL
BEGIN
	SET @Mensagem = 'Resposta';

	SEND ON CONVERSATION @MensagemId
		MESSAGE TYPE Resposta(@Mensagem);
END

REVERT;

GO

USE DB01;

EXEC AS LOGIN = 'Maria';

USE DB01;

DECLARE @Mensagem XML
, @MensagemId UNIQUEIDENTIFIER;

RECEIVE TOP(1)
	@Mensagem = [message_body],
	@MensagemId = [conversation_handle]
FROM FilaResposta;

SELECT @MensagemId, @Mensagem;

IF @MensagemId IS NOT NULL
	END CONVERSATION @MensagemId;

REVERT;

GO

Introdução ao Service Broker – Parte 1 – Monólogo

Não se trata da feature que mais me agrada do SQL Server 2005+, mas possui um potencial interessante, visto que se trata de uma feature nativa de mensageria, onde é possível basicamente enviar mensagens para um mesmo banco de dados, para bancos de dados diferentes (no mesmo servidor ou mais servidores) com pouca complexidade, e por meio destas mensagens executar processos assíncronos e de forma escalável.

Para um exemplo simples, vou tratar um monólogo (1), onde enviaremos uma mensagem para uma fila de mensagens, e em algum momento esta mensagem poderá ser recuperada para ser processada por alguma aplicação que trate estas mensagens.

(1): Na verdade, se tratada de um pseudo-monólogo, pois a comunicação do Service Broker permite um dialogo onde os serviços de origem e de destino sejam o mesmo serviço.

Primeiro, o que seriam estas mensagens? As mensagens podem ter dados dos mais variados tipos, com a finalidade de quem a receber, possa realizar alguma ação com ela.

Para haver uma comunicação é necessário definir formatos adequados para estas mensagens, assim possibilitando que quem a receber consiga saber o que fazer com ela. No caso do Service Broker, definimos tipos de mensagens, permitindo a validação se o que estamos comunicando esta de acordo com o esperado.

Na definição do tipo de mensagem, podemos utilizar um dos tipos de validações possíveis:

  1. NONE: Sem validação
  2. EMPTY: Mensagens devem ser vazias (NULL)
  3. WELL_FORMED_XML: Mensagens devem ser no formato XML
  4. VALID_XML WITH SCHEMA COLLECTION: Mensagens devem ser no formato XML obedecendo a um determinado XML Schema (XSD).

Para um exemplo simples, utilizaremos mensagens no formato XML:

USE [master];

--Criando o banco de dados
CREATE DATABASE DB01;

GO

--Habilitando o service broker
ALTER DATABASE DB01
	SET ENABLE_BROKER;

GO

USE DB01;

--Criando a master key
CREATE MASTER KEY ENCRYPTION BY PASSWORD = '2u93r23nh4';

GO

USE DB01;

--Criando o tipo de mensagem
CREATE MESSAGE TYPE Requisicao
	VALIDATION = WELL_FORMED_XML;

GO

Para definir qual tipo de mensagem será enviada e qual tipo de mensagem será recebida, criaremos um contrato. No caso do monologo, definiremos somente qual o tipo da mensagens que iniciará a comunicação.

CREATE CONTRACT Contrato (
	Requisicao SENT BY INITIATOR
);

Definimos a seguir a fila de mensagens (caixa de correio) onde as mensagens serão armazenadas:

CREATE QUEUE FilaRequisicao
	WITH STATUS = ON;

E por fim criamos nosso serviço de mensageria (carteiro), onde definimos nossa fila e o que será armazenado nela por meio dos contratos.

CREATE SERVICE ServicoRequisicao
	ON QUEUE FilaRequisicao (Contrato);
GO

Agora que a estrutura esta completa, enviamos uma mensagem:

DECLARE @Mensagem XML = 'Requisição'
, @MensagemId UNIQUEIDENTIFIER;

--Criando uma conversa
BEGIN DIALOG CONVERSATION @MensagemId
	FROM SERVICE ServicoRequisicao
	TO SERVICE 'ServicoRequisicao'
	ON CONTRACT Contrato;

--Enviando a mensagem
SEND ON CONVERSATION @MensagemId
	MESSAGE TYPE Requisicao(@Mensagem);

GO

E em outro momento, a recebemos:

DECLARE @Mensagem XML
, @MensagemId UNIQUEIDENTIFIER;

--Recebendo a mensagem
RECEIVE TOP(1)
	@Mensagem = [message_body],
	@MensagemId = [conversation_handle]
FROM FilaRequisicao;

--Exbindo a mensagem
SELECT @MensagemId, @Mensagem;

--Finalizando a conversa
IF @MensagemId IS NOT NULL
	END CONVERSATION @MensagemId

GO

Como este foi um exemplo simples, é possível que você esteja se perguntando “para que vou usar isso?”, mas fique tranquilo pois com os próximos artigos ficará mais claro os cenários onde é possível usar esta feature.

Mais informações:

Vantagens do Service Broker
http://msdn.microsoft.com/pt-br/library/ms166063.aspx

Qual a função do Service Broker?
http://msdn.microsoft.com/pt-br/library/ms166049.aspx