En el artículo anterior, discutimos el uso de transacciones para proteger la integridad y consistencia de la comunicación basada en Service Broker en SQL Server 2005 Express Edition. Presentamos un código de muestra que demostraba cómo iniciar una conversación, enviar un mensaje a un destino y almacenar su contenido en una tabla auxiliar local, todo dentro de una sola transacción. Sin embargo, también mencionamos que dependiendo del tipo de problema encontrado, el resultado podría ser diferente al esperado.
En este artículo, demostraremos un enfoque más robusto para el manejo de errores y lo aplicaremos a nuestro objetivo. Encerraremos tanto la recuperación de nuevos mensajes de la cola como la inserción de su contenido en una tabla dentro de una sola transacción. Esto evitará situaciones en las que se recibe un mensaje correctamente pero no tiene una entrada correspondiente en la tabla.
Para manejar los errores de manera más efectiva, utilizaremos los bloques BEGIN TRY/END TRY y BEGIN CATCH/END CATCH, así como introduciremos mejoras adicionales a nuestro código. Capturaremos información más específica sobre un problema invocando las funciones ERROR_NUMBER() y ERROR_MESSAGE(), que devuelven el número de referencia del error y su descripción breve, respectivamente. Esta información se transmitirá al servicio iniciador a través de un mensaje de Service Broker.
Además, utilizaremos la función XACT_STATE(), que devuelve el estado de la transacción actual. Según el valor devuelto por XACT_STATE(), decidiremos si forzar un rollback de la transacción o confirmarla. También introduciremos un mecanismo rudimentario para rastrear mensajes fallidos.
Aquí hay un ejemplo del código:
ALTER PROCEDURE dbo.cspProcessqRecv WITH EXECUTE AS OWNER AS
BEGIN
DECLARE @convHandle UNIQUEIDENTIFIER;
DECLARE @msgTypeName SYSNAME;
DECLARE @status TINYINT;
DECLARE @srvName NVARCHAR(512);
DECLARE @srvConName NVARCHAR(256);
DECLARE @msgTypeValidation AS NCHAR(2);
DECLARE @msgBody NVARCHAR(400);
DECLARE @cmd AS NVARCHAR(50);
DECLARE @errNum AS INTEGER;
DECLARE @errDesc AS NVARCHAR(400);
BEGIN TRY
BEGIN TRANSACTION;
WAITFOR(RECEIVE TOP(1) @convHandle = conversation_handle, @msgTypeName = message_type_name, @status = status, @srvName = service_name, @srvConName = service_contract_name, @msgTypeValidation = validation, @msgBody = CAST(message_body AS NVARCHAR(400)) FROM qRecv), TIMEOUT 1000;
IF(@@rowcount != 0)
BEGIN
INSERT INTO tbMsgRecv(convHandle, msgTypeName, [status], srvName, srvConName, msgTypeValidation, msgBody)
VALUES(@convHandle, @msgTypeName, @status, @srvName, @srvConName, @msgTypeValidation, @msgbody);
END
CONVERSATION @convHandle;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
SET @errNum = ERROR_NUMBER();
SET @errDesc = ERROR_MESSAGE();
IF (XACT_STATE()) = -1
BEGIN
ROLLBACK TRANSACTION;
BEGIN TRANSACTION;
RECEIVE TOP(1) @msgBody = CAST(message_body AS NVARCHAR(400)) FROM qRecv WHERE conversation_handle = @convHandle;
SEND ON CONVERSATION @convHandle MESSAGE TYPE [//databaseJournal.com/SQL2005EX/ServiceBroker/msgNV] (@msgBody)
END
CONVERSATION @convHandle WITH ERROR = @errNum DESCRIPTION = @errDesc;
COMMIT TRANSACTION;
END
IF (XACT_STATE()) = 1
BEGIN
INSERT INTO tbMsgRecvErr(convHandle, msgTypeName, srvName, srvConName, msgTypeValidation, msgBody)
VALUES(@convHandle, @msgTypeName, @srvName, @srvConName, @msgTypeValidation, @msgBody);
END
CONVERSATION @convHandle WITH ERROR = @errNum DESCRIPTION = @errDesc;
COMMIT TRANSACTION;
END
Bajo circunstancias favorables, el código encerrado dentro del bloque BEGIN TRY/END TRY extraerá un mensaje de la cola, ingresará su contenido en una tabla y finalizará la conversación. Si ocurre un error que hace que la transacción no se pueda confirmar, se invocará el rollback, colocando el mensaje nuevamente en la cola. Lo procesaremos utilizando otra instrucción RECEIVE y lo enviaremos de vuelta al servicio iniciador junto con un mensaje de error que termina la conversación actual.
Si la transacción aún se puede confirmar, lo haremos después de insertar el mensaje procesado en otra tabla auxiliar, finalizar la conversación y enviar un mensaje de error al servicio iniciador.
Este ejemplo se puede refinar y mejorar aún más para manejar diferentes tipos de mensajes y condiciones de error. Es importante asegurarse de que el iniciador y el destino puedan reaccionar adecuadamente a cada tipo de mensaje devuelto. Alternativamente, puede confiar en el mecanismo incorporado de Service Broker conocido como Detección de mensajes envenenados para manejar condiciones de error.
En el próximo artículo de nuestra serie, exploraremos la Detección de mensajes envenenados con más detalle.