Desenvolvimento - C#

WCF - Message Queue

Para garantir a entrega da mensagem e o processamento assíncrono da operação (mesmo quando o serviço estiver offline), o WCF faz uso do Microsoft Message Queue. Este artigo irá explorar as funcionalidades e, principalmente, os benefícios fornecidos por essa integração.

por Israel Aéce



function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i Ao efetuar uma chamada para uma operação de um determinado serviço, desejamos que ela seja sempre executada. Mas nem sempre há como garantir isso, já que o serviço que atende as requisições, por algum motivo, está indisponível naquele momento. Isso fará com que as requisições sejam rejeitadas e o cliente somente conseguirá executá-la quando o serviço estiver novamente no ar. Para garantir a entrega da mensagem e o processamento assíncrono da operação (mesmo quando o serviço estiver offline), o WCF faz uso do Microsoft Message Queue. Este artigo irá explorar as funcionalidades e, principalmente, os benefícios fornecidos por essa integração.

O Microsoft Message Queue é uma tecnologia que está ligada diretamente ao sistema operacional que, entre suas diversas funcionalidades, temos a durabilidade, suporte à transações, garantia de entrega, etc. O Message Queue é um componente adicional, e que pode ser instalado a partir dos recursos do Windows. O Windows XP e 2003 trazem a versão 3.0 do Message Queue, enquanto o Windows Vista e 2008 disponibilizam a versão 4.0.

A disponibilidade é uma das principais características de uma aplicação que a faz utilizar o Message Queue. O fato do serviço não estar online nem sempre é um problema; é perfeitamente possível que algum cliente seja um dispositivo móvel, fazendo com que o serviço esteja inalcançável e, com isso, em um ambiente tradicional, qualquer chamada para alguma operação iria falhar. Com a durabilidade, o Message Queue garante que a mensagem seja persistida fisicamente e, quando a conexão for restabelecida, a mesma será enviada para o serviço.

Podemos interagir com o Message Queue de duas formas: a primeira é utilizando a console de gerenciamento que é criada quando você instala o Message Queue; já a segunda é através do .NET, que fornece um Assembly chamado System.Messaging.dll com vários tipos para criar, enviar e remover mensagem de uma fila. O namespace System.Messaging existe desde a versão 1.0 do .NET Framework, mas o WCF encapsula o uso dele e, felizmente, não precisaremos recorrer a qualquer classe deste namespace para fazer com que o Message Queue funcione em conjunto com o WCF.

Filas Públicas e Privadas

O Message Queue possibilita a criação de dois tipos de filas, a saber: públicas e privadas. As filas públicas obrigatoriamente devem estar registradas em um domínio, através do Active Directory, podendo ser acessadas por todas as máquinas que estão sob aquele domínio. Já as filas privadas tem um escopo bem mais restrito, ou seja, podem ser acessadas apenas dentro da máquina onde elas foram criadas.

Durante a criação de uma fila, via console de gerenciamento ou através da API System.Messaging, podemos definir se a mesma será ou não transacionada. Marcando a fila como transacionada, tanto a inserção de uma nova mensagem com a remoção de uma mensagem existente será protegida por uma transação. Quando falamos especificamente sobre transações no WCF com o Message Queue, há alguns detalhes que temos que nos atentar e que veremos mais tarde, ainda neste mesmo artigo.

Chamadas Enfileiradas e Processamento Assíncrono

Ao invocar uma operação onde temos o Message Queue envolvido, ele trará vários benefícios. Em um formato tradicional, utilizando outros protocolos mais convencionais, como o HTTP, TCP ou IPC, demanda que o serviço esteja disponível para que a mensagem chegue até ele e seja processada e, caso contrário, uma exceção será disparada no cliente.

Com a integração do Message Queue, o WCF persistirá a mensagem localmente em uma fila caso o serviço não esteja disponível. Ao persistir a mensagem, a garantia de entrega será assegurada pelo Message Queue de forma transparente para a aplicação cliente e, quando o serviço ficar novamente ativo, a mensagem será encaminhada para o serviço para efetuar o processamento da(s) operação(ões) (algo que já era suportado no COM+). É importante dizer que não há uma relação entre chamada à uma operação e uma mensagem na fila do Message Queue; poderá haver mensagens que acomodarão mais que uma operação (falaremos detalhadamente sobre isso mais tarde, ainda neste artigo) A imagem abaixo ilustra superficialmente como as mensagens são enviadas/recebidas quando o serviço é exposto via Message Queue:

Figura 1 - Serviço exposto via Message Queue.

Como a fila em que as operações serão persistidas estará sempre disponível, o WCF sempre armazenará localmente e, com isso, a aplicação poderá continuar trabalhando sem esperar que a mensagem seja entregue, garantindo assim o que chamamos de processamento assíncrono. Pelo fato das mensagens estarem persistidas, elas conseguirão sobreviver a possíveis reinicializações do cliente e, quando o mesmo retornar, novas tentativas serão realizadas até que a mensagem seja efetivamente entregue ao destino. Obviamente que se o cliente e o serviço estiverem online, a mensagem será entregue imediatamente.

MSMQ e o WCF

Quando formos desenhar um contrato para ser exposto através do Message Queue, um cuidado que devemos ter é com relação ao tipo da operação. No tópico anterior falamos sobre as necessidades da utilização do Message Queue e, analisando essas características, vemos que as operações que serão expostas através do Message Queue não devem retornar nenhum resultado, e também possíveis exceções nunca chegarão até o cliente, já o WCF desabilita os contratos de faults em operações enfileiradas. A finalidade do contrato é apenas definir a semântica da aplicação e, durante a execução, a chamada poderá ou não ser persistida e mais tarde processada.

Com isso, o WCF nos obriga a definir todas as operações de um contrato que serão expostas através do Message Queue como sendo one-way (mais detalhes neste artigo). Caso você exponha uma das operações sem antes definí-la como one-way, uma exceção do tipo InvalidOperationException será disparada antes da abertura do host. Com exceção deste detalhe, não há nada diferente a ser realizado em relação à implementação ou chamadas às operações enfileiradas. Note que o código abaixo exibe a criação deste contrato:

using System;
using System.ServiceModel;

[ServiceContract]
[DeliveryRequirements(QueuedDeliveryRequirements = 
QueuedDeliveryRequirementsMode.Required)]
public interface IContrato
{
    [OperationContract(IsOneWay = true)]
    void EnviarDados(string msg);
}
Imports System
Imports System.ServiceModel

<ServiceContract(), _
    DeliveryRequirements(QueuedDeliveryRequirements:=QueuedDeliveryRequirementsMode.Required)> _
Public Interface IContrato
    <OperationContract(IsOneWay:=True)>
    Sub EnviarDados(ByVal msg As String)
End Interface
C# VB.NET

O WCF também permite a você especificar no contrato que o mesmo deverá ser exposto sob um binding que suporte chamadas enfileiradas. Para isso, basta recorrermos ao atributo DeliveryRequirementsAttribute, definindo a propriedade QueuedDeliveryRequirements com uma das três opções definidas no enumerador QueuedDeliveryRequirementsMode:

  • Allowed: O binding pode ou não suportar chamadas enfileiradas.

  • Required: O binding deve suportar chamadas enfileiradas.

  • NotAllowed: O binding não deve suportar chamadas enfileiradas.

Esse atributo ainda fornece duas outras propriedades: RequireOrderedDelivery e TargetContract. A primeira propriedade determina se o binding deverá ou não garantir a entrega ordenada das mensagens. Já a segunda propriedade espera um objeto do tipo Type, que determina em qual contrato essa técnica será aplicada. Essa propriedade somente faz sentido quando o atributo é aplicado na classe que representa o serviço, ao invés do contrato.

Hosting e Binding

Como já sabemos, o binding contém os aspectos de comunicação, especificando o meio de transporte, codificação, etc. Para expor um serviço via Message Queue, devemos recorrer a um binding exclusivo para isso, o NetMsmqBinding. Apesar de não expor todas as propriedades suportadas pelo Message Queue, este binding traz as principais funcionalidades necessárias para a utilização do mesmo através do WCF.

Para especificar o endereço onde o serviço será exposto, devemos utilizar a seguinte convenção (note que não há o caracter $): net.msmq://NomeDaMaquina/Private/NomeDaFila. Como o WCF não pode publicar o documento WSDL através do Message Queue, é necessária a criação de um endpoint exclusivo para a publicação do mesmo, através de algum outro protocolo, como o HTTP ou TCP. Caso isso não seja feito, os clientes não conseguirão referenciar o serviço e criar o proxy. O trecho de código abaixo ilustra como devemos proceder para configurar o host:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

using (ServiceHost host =
    new ServiceHost(typeof(Servico), new Uri[] { 
        new Uri("net.msmq://localhost/private/FilaDeTestes"), 
        new Uri("http://localhost:8383/") }))
{
    host.Description.Behaviors.Add(new ServiceMetadataBehavior());

    host.AddServiceEndpoint(
        typeof(IContrato), 
        new NetMsmqBinding(NetMsmqSecurityMode.None), 
        string.Empty);

    host.AddServiceEndpoint(
        typeof(IMetadataExchange), 
        MetadataExchangeBindings.CreateMexHttpBinding(), 
        "mex");

    host.Open();
    Console.ReadLine();
}
Imports System
Imports System.ServiceModel
Imports System.ServiceModel.Description

Using host As New ServiceHost(GetType(Servico), New Uri() { _
                              New Uri("net.msmq://localhost/private/FilaDeTestes"), _
                              New Uri("http://localhost:8383/")})

    host.Description.Behaviors.Add(New ServiceMetadataBehavior())

    host.AddServiceEndpoint( _
        GetType(IContrato), _
        New NetMsmqBinding(NetMsmqSecurityMode.None), _
        String.Empty)

    host.AddServiceEndpoint( _
        GetType(IMetadataExchange), _
        MetadataExchangeBindings.CreateMexHttpBinding(), _
        "mex")

    host.Open()
    Console.ReadLine()
Using
C# VB.NET

A configuração do host não tem muitas diferenças em relação a um serviço exposto via qualquer outro tipo de binding. Estamos utilizando o NetMsmqBinding com sua configuração padrão, ou seja, nenhuma das propriedades expostas por ele foi customizada. Como falamos acima, este binding traz várias propriedades que podem ser configuradas (de forma imperativa ou declarativa) para customizar o envio/processamento das mensagens. A tabela abaixo lista essas propriedades e suas respectivas descrições:

Propriedade Descrição
CustomDeadLetterQueue

Recebe uma instância da classe Uri (com o formato "net.msmq") representando uma dead-letter queue customizada a ser utilizada pela aplicação. Essa propriedade trabalhará em conjunto com a propriedade DeadLetterQueue.

DeadLetterQueue

Esta propriedade irá especificar o tipo da dead-letter queue. O tipo poderá ser definido com uma das três opções fornecidas pelo enumerador DeadLetterQueue:

  • Nome: A dead-letter queue não é requerida. Se a entrega da mensagem falhar, ela não será gravada em lugar algum. Essa opção é utilizada quando a propriedade ExactlyOnce é definida como False.

  • System: Determina que a dead-letter queue do sistema será utilizada para armazenar as mensagens que falharem. Há dead-letter queue transacional e não transacional. Quando a propriedade ExactlyOnce estiver definida como True, essa opção será utilizada.

  • Custom: Nesta opção você pode especificar uma fila própria para ser a sua dead-letter queue. Esta opção trabalhará em conjunto com a propriedade CustomDeadLetterQueue.

Durable

Propriedade do tipo booleana que quando definida como True (padrão), o binding irá garantir a durabilidade da mensagem persistindo-a no disco. Quando definida como False, a mensagem será armazenada de forma "volátil", ou seja, ela não conseguirá sobreviver a possíveis reinicializações do serviço do Message Queue.

ExactlyOnce

Outra propriedade do tipo booleana e, quando definida como True (padrão), garantirá que uma vez que a mensagem for entregue ao serviço, ela não será duplicada. Caso a mensagem não seja entregue por algum motivo, a mesma será movida para a dead-letter queue. Devido a finalidade desta propriedade, obrigatoriamente a fila deverá ser transacional.

MaxRetryCycles

A tentativa de processamento/entrega da mensagem (da fila para o serviço) consiste em ciclos, e cada ciclo contém um número de tentativas. Esta propriedade define um número inteiro que especifica a quantidade de ciclos de tentativas de entrega/processamento a serem realizadas. Como valor padrão, esta propriedade possui 2 ciclos.

QueueTransferProtocol

Esta propriedade determinará qual será o meio de comunicação utilizado pelos gerenciadores do Message Queue de ambas as partes. Essa propriedade poderá ser configurada com uma das opções definidas pelo enumerador QueueTransferProtocol:

  • Native: Utiliza o protocolo nativo do Message Queue (padrão).
  • Srmp: Utiliza o protocolo Soap Reliable Messaging Protocol.
  • SrmpSecure: Utiliza o protocolo Soap Reliable Messaging Protocol Secure.

Os protocolos SRMP e SRMPS são utilizados quando desejamos expor a fila através do protocolo HTTP, mas isso está fora do escopo deste artigo. Caso a propriedade UseActiveDirectory estiver definida como True e algum protocolo diferente do Native for utilizado, uma exceção será disparada.

ReceiveErrorHandling

Esta propriedade determinará o comportamento que o serviço WCF deverá ter quando todas as tentativas de entrega/processamento se esgotarem. Essa propriedade aceita uma das quatro opções fornecidas pelo enumerador ReceiveErrorHandling:

  • Fault: Quando um erro for encontrado ao processar a mensagem e a opção Fault estiver definida, o host será comprometido e a mensagem deverá ser removida da fila através do administrador ou outra aplicação antes de continuar a execução e, além disso, como esta opção compromete a vida do host, ele deverá ser reinicializado. Nenhuma notificação (Acknowledgement) do problema será enviada ao cliente.

  • Drop: Como o próprio nome indica, essa mensagem será efetivamente excluída da fila, continuando o processamento das outras mensagens. O Drop notifica (Acknowledgement) o cliente mas, do seu ponto de vista, a mensagem foi processada com sucesso. A mensagem será colocada na dead-letter queue do cliente caso a mensagem não tenha expirado (TimeToLive); caso contrário, ela não aparecerá em lugar algum.

  • Reject: Envia uma notificação (Acknowledgement) para o cliente informando que a mensagem não pode ser recebida pela aplicação (serviço). A mensagem é colocada na dead-letter queue do cliente. O cliente receberá uma notificação (Acknowledgement) negativa informando que a mensagem não pode ser processada. A mensagem será colocada na dead-letter queue do cliente.

  • Move: Move a mensagem para a Poison Message Queue, permitindo que a mensagem seja analisada posteriormente. Falaremos mais sobre esse tipo especial de fila ainda neste artigo. Nenhuma notificação (Acknowledgement) será enviada ao cliente, pois a idéia é enviá-la quando a mensagem poison for processada.

ReceiveRetryCount

Cada ciclo (especificado através da propriedade MaxRetryCycles) possui uma quantidade de tentativas. Através desta propriedade, conseguimos determinar a quantidade de tentativas de cada ciclo. Como valor padrão, esta propriedade possui 5 tentativas.

RetryCycleDelay

Esta propriedade define um intervalo de tempo (TimeSpan) entre os ciclos de tentativas. Como valor padrão, esta propriedade está definida como 10 minutos.

TimeToLive

Essa propriedade define um TimeSpan indicando o período em que a mensagem irá expirar. Mensagens que estão dentro da fila e que não são acessadas por uma aplicação dentro do intervalo especificado serão expiradas, movendo-as para a dead-letter queue. Quando omitido, o valor padrão desta propriedade é 1 dia.

UseActiveDirectory

O endereço do Message Queue consiste em dois formatos: path names ou direct format names. A primeira opção faz com que o Message Queue utilize o Active Directory para resolver o nome da fila (exemplo: NomeDoComputador\private$\NomeDaFila), enquanto a segunda opção, tentará resolver o nome da fila utilizando o DNS ou o IP (exemplo: FormatName:Direct=TCP:192.168.1.3\NomeDaFila).

Por padrão, o WCF converte a URI do serviço no formato direct format name e, através da propriedade UseActiveDirectory você poderá definir um valor booleano (que por padrão é False), fazendo com que ele utilize o formato path name. Algumas funcionalidades disponibilizadas pelo binding NetMsmqBinding, como é o caso da criptografia de mensagens utilizando a segurança a nível de transporte, somente funcionarão se utilizar o Active Directory.

UseMsmqTracing

Valor booleano que indica se o processamento das mensagens será ou não logado. O valor padrão é False.

UseSourceJournal

Journaling é um recurso do Message Queue que permite salvar uma cópia das mensagens entregues com sucesso ao destino. Por padrão essa funcionalidade está desabilitada, mas você pode definir a propriedade UseSourceJournal como True para colocar em funcionamento este recurso.

Dentre as propriedades acima, algumas se referem a dois tipos especiais de filas: dead-letter queue e poison message queue. Esses tipos especiais, criados pelo sistema, são extremamente importantes para garantir o funcionamento de algumas configurações expostas pelo Message Queue. Em outras palavras, as dead-letter queues lidam com problemas relativos à comunicação e as poison message queues se limitam a tratar problemas que ocorrem dentro da execução da operação.

Há vários problemas que podem acontecer durante a tentativa de entrega da mensagem; entre esses problemas temos falhas na infraestrutura, a fila foi excluída, falha na autenticação, etc. Esses tipos de problemas fazem com que a mensagem seja enviada para uma fila especial, chamada de dead-letter queue, ficando a mensagem ali até o momento em que uma outra aplicação ou o administrador do sistema tome alguma decisão (atente-se a expiração que a mensagem poderá ter). O Windows já disponibiliza dois tipos de dead-letter queue: Dead-letter messages para mensagens não transacionadas e Transactional dead-letter messages para mensagens transacionadas.

Como as filas que mencionamos acima são fornecidas pelo próprio sistema operacional, elas são compartilhadas entre todas as aplicações que rodam naquela máquina. Apesar da fila aceitar as mensagens independente de onde elas vieram, ficará difícil a manutenção nelas. Muitas aplicações já fornecem o suporte para processamento das mensagens nesta fila, mas como distinguir qual mensagem pertence àquela aplicação? Visando sanar este problema é que recorremos à criação de uma fila customizada (através das propriedades DeadLetterQueue e CustomDeadLetterQueue) para catalogar as mensagens problemáticas, fornecendo um isolamento entre as aplicações.

Quando optamos por criar uma fila customizada para servir como dead-letter queue, esta é como uma fila normal, não havendo nada de especial, mas atentando-se à definí-la ou não como transacional, dependendo da sua necessidade. A configuração do binding muda ligeiramente, definindo agora a fila customizada como dead-letter queue, assim como é mostrado no código:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

using (ServiceHost host =
    new ServiceHost(typeof(Servico), new Uri[] { 
        new Uri("net.msmq://localhost/private/FilaDeTestes"), 
        new Uri("http://localhost:8383/") }))
{
    host.Description.Behaviors.Add(new ServiceMetadataBehavior());

    NetMsmqBinding binding = new NetMsmqBinding(NetMsmqSecurityMode.None);
    binding.DeadLetterQueue = DeadLetterQueue.Custom;
    binding.CustomDeadLetterQueue = 
        new Uri("net.msmq://localhost/private/MensagensProblematicas");

    host.AddServiceEndpoint(
        typeof(IContrato),
        binding,
        string.Empty);

    host.AddServiceEndpoint(
        typeof(IMetadataExchange), 
        MetadataExchangeBindings.CreateMexHttpBinding(), 
        "mex");

    host.Open();
    Console.ReadLine();
}
Imports System
Imports System.ServiceModel
Imports System.ServiceModel.Description

Using host As New ServiceHost(GetType(Servico), New Uri() { _
                              New Uri("net.msmq://localhost/private/FilaDeTestes"), _
                              New Uri("http://localhost:8383/")})

    host.Description.Behaviors.Add(New ServiceMetadataBehavior())

    Dim binding As New NetMsmqBinding(NetMsmqSecurityMode.None)
    binding.DeadLetterQueue = DeadLetterQueue.Custom
    binding.CustomDeadLetterQueue = _
        New Uri("net.msmq://localhost/private/MensagensProblematicas")

    host.AddServiceEndpoint( _
        GetType(IContrato), _
        binding, _
        String.Empty)

    host.AddServiceEndpoint( _
        GetType(IMetadataExchange), _
        MetadataExchangeBindings.CreateMexHttpBinding(), _
        "mex")

    host.Open()
    Console.ReadLine()
Using
C# VB.NET

Observações: Lembre-se de que a fila criada para servir como dead-letter queue é uma fila normal e, para processar as mensagens que estão dentro dela, basta criar um novo serviço que extraia as mensagens e efetue o devido processamento. Se por algum motivo você queira acessar as filas de dead-letter do sistema, você poderá acessá-la partir dos seguintes endereços: net.msmq://localhost/system$;DeadLetter (Dead-letter messages) e net.msmq://localhost/system$;DeadXact (Transactional dead-letter messages).

Ainda falando sobre filas especiais, temos a poison queue. Há mensagens que podem falhar durante o processamento por vários motivos. Por exemplo, ao processar uma mensagem e salvar algumas informações em um banco de dados, algum problema pode acontecer, como é o caso de um deadlock, fazendo com que a transação seja abortada e a mensagem seja devolvida para a fila. Isso fará com que a mensagem seja novamente reprocessada e dependendo do problema que está acontecendo e não havendo estratégia para remover a mensagem da fila, poderemos ter um loop infinito.

Para evitar que problemas como este ocorram, o Message Queue possui algumas configurações que permitem determinar a quantidade de tentativas e, quando elas se esgotarem, a mensagem é enviada para uma fila do tipo poison. Para lidar com esta técnica, o Message Queue cria duas "sub-filas" abaixo da fila principal, chamadas de retry e poison. A primeira "sub-fila" armazenará as mensagens que estão em uma de suas tentativas de processamento; já a segunda "sub-fila", poison, armazenará as mensagens que não foram processadas com sucesso, mesmo depois de todas as tentativas, evitando assim que o loop infinito não aconteça. O exemplo abaixo ilustra como configurar o binding para suportar essa técnica:

using System;
using System.ServiceModel;
using System.ServiceModel.Description;

using (ServiceHost host =
    new ServiceHost(typeof(Servico), new Uri[] { 
        new Uri("net.msmq://localhost/private/FilaDeTestes"), 
        new Uri("http://localhost:8383/") }))
{
    host.Description.Behaviors.Add(new ServiceMetadataBehavior());

    NetMsmqBinding binding = new NetMsmqBinding(NetMsmqSecurityMode.None);
    binding.DeadLetterQueue = DeadLetterQueue.Custom;
    binding.CustomDeadLetterQueue = 
        new Uri("net.msmq://localhost/private/MensagensProblematicas");

    binding.MaxRetryCycles = 2;
    binding.ReceiveRetryCount = 2;
    binding.RetryCycleDelay = TimeSpan.FromSeconds(10);
    binding.ReceiveErrorHandling = ReceiveErrorHandling.Move;

    host.AddServiceEndpoint(
        typeof(IContrato),
        binding,
        string.Empty);

    host.AddServiceEndpoint(
        typeof(IMetadataExchange), 
        MetadataExchangeBindings.CreateMexHttpBinding(), 
        "mex");

    host.Open();
    Console.ReadLine();
}
Imports System
Imports System.ServiceModel
Imports System.ServiceModel.Description

Using host As New ServiceHost(GetType(Servico), New Uri() { _
                              New Uri("net.msmq://localhost/private/FilaDeTestes"), _
                              New Uri("http://localhost:8383/")})

    host.Description.Behaviors.Add(New ServiceMetadataBehavior())

    Dim binding As New NetMsmqBinding(NetMsmqSecurityMode.None)
    binding.DeadLetterQueue = DeadLetterQueue.Custom
    binding.CustomDeadLetterQueue = _
        New Uri("net.msmq://localhost/private/MensagensProblematicas")

    binding.MaxRetryCycles = 2
    binding.ReceiveRetryCount = 2
    binding.RetryCycleDelay = TimeSpan.FromSeconds(10)
    binding.ReceiveErrorHandling = ReceiveErrorHandling.Move

    host.AddServiceEndpoint( _
        GetType(IContrato), _
        binding, _
        String.Empty)

    host.AddServiceEndpoint( _
        GetType(IMetadataExchange), _
        MetadataExchangeBindings.CreateMexHttpBinding(), _
        "mex")

    host.Open()
    Console.ReadLine()
Using
C# VB.NET

Observação: Uma vez que as mensagens são movidas para uma fila do tipo poison, elas somente poderão ser acessadas acrescentando a palavra poison no final do nome da fila, separando por um ";", assim como é mostrado a seguir: net.msmq://localhost/private/FilaDeTestes;poison.

Ainda há a possibilidade de efetuar o hosting de um serviço que utiliza o Message Queue utilizando o WPAS (Windows Process Activation Service), fazendo com que o serviço seja exposto através do IIS, tirando proveito de todos os benefícios fornecidos por ele. Essa opção está desabilitada e é necessário instalar este recurso explicitamente a partir do Windows. Isso fará com que um novo serviço, chamado Net.Msmq Listener Adapter, seja instalado e deverá estar funcionando para permitir o serviço.

Transações

O Message Queue também é considerado um resource manager transacional. Isso quer dizer que tanto a entrada quanto a extração de uma mensagem na fila poderá ser envolvida através de uma transação. Como já falado anteriormente, isso somente será possível se durante a criação da fila você especifique que a mesma seja uma fila transacional.

Quando estamos falando de uma fila transacional, temos alguns detalhes e técnicas que podemos fazer uso para tirar o melhor proveito das transações. Antes de mais nada, precisamos entender como as transações estão distribuídas durante o processo de criação, entrega e processamento da mensagem. Cada uma destas etapas exige uma transação e, para ter uma visão mais detalhada, vamos analisar a mesma imagem que vimos acima só que exibindo onde estão essas possíveis transações:

Figura 2 - Transações que envolvem Message Queue quando exposto via WCF.

  • 1 - Client Transaction: Caso a chamada para a operação esteja envolvida em uma transação, a inserção da mensagem no Message Queue também será protegida por esta mesma transação. Depois da mensagem persistida no Message Queue e, se por algum motivo, a transação for abortada, automaticamente a mensagem será descartada. Felizmente a tentativa de entrega não acontecerá até que a transação seja "comitada".

  • 2 - Delivery Transaction: A transação neste caso protegerá a entrega da mensagem entre o cliente e o servidor (obviamente quando a fila for transacional). Se a entrega falhar por qualquer razão, a mensagem será seguramente devolvida para o cliente e, mais tarde, o Message Queue efetuará uma nova tentativa.

  • 3 - Playback Transaction: Uma vez que a mensagem foi entregue com sucesso para o servidor, entra em cena uma nova transação, chamada de playback transaction. A finalidade desta transação é proteger a mensagem durante o processamento da mesma. Se qualquer problema acontecer durante a execução da operação, a mensagem será devolvida para a fila, valendo a partir daqui o mecanismo de tentativas automáticas, que vimos anteriormente.

Uma questão que aparece quando isso é apresentado é como fazer parte da transação já criada pela própria plataforma ou como criar uma nova transação. Com exceção do processo 2 (Delivery Transaction), podemos criar códigos para fazer parte da transação que coloca a mensagem na fila quanto remover a mensagem dela. Para que isso seja possível não há muito segredo, bastando apenas recorrer à alguns tipos fornecidos pelo próprio WCF como pelo namespace System.Transactions e que já foram detalhadamente falados neste artigo.

Se quisermos criar um código que faça parte da mesma transação que coloca a mensagem na fila do lado do cliente (passo 1), basta instanciar a classe TransactionScope e envolver a chamada da operação dentro deste escopo transacional. Já no passo 3, para que o processamento da operação faça parte da mesma transação que é usada para extrair a mensagem, basta definirmos para True a propriedade TransactionScopeRequired do atributo OperationBehaviorAttribute que, por definição, se existir uma transação em aberto, a operação fará parte da mesma. Finalmente, se quisermos que a operação execute dentro de uma nova transação, basta criarmos um escopo transacionado através da classe TransactionScope e, em seu construtor, especificamos a opção RequiresNew, fornecida pelo enumerador TransactionScopeOption.

Gerenciamento de Instâncias

A escolha do modo de gerenciamento de instâncias do serviço implicará durante a execução do processamento das operações. Quando o serviço é exposto através do modo PerCall, cada chamada a qualquer operação será criada uma nova mensagem dentro da fila. Já no modo PerSession, se a sessão for requerida, as operações invocadas a partir de uma instância do proxy serão agrupadas em uma única mensagem. Finalmente, como o modo Single não pode definir uma sessão, cada chamada será sempre mapeada para uma mensagem dentro da fila.

Quando o host (ServiceHost) é criado para expor um serviço sob o Message Queue, o WCF cria de forma transparente um listener chamado MSMQ Channel Listener e tem um papel extremamente importante durante o processamento das mensagens. Como sabemos, cada modo de gerenciamento determina a criação da instância da classe que representa o serviço para atender às requisições. Este listener é responsável por extrair as mensagens da fila, criar a instância da classe e encaminhar as chamadas que estão na mensagem do Message Queue e encaminhá-las para sua respectiva instância.

Conclusão: Como pudemos ver no decorrer deste artigo, o WCF permite uma forte integração com o Message Queue para enriquecer ainda mais as características de um serviço, incrementado-o com a garantia de entrega da mensagem e ordenação, durabilidade, processamento assíncrono, chamadas enfileiradas e podendo tudo isso ser envolvido por transações para assegurar a consistência do processo. E, por fim, o uso do Message Queue permitirá aos clientes continuarem seu trabalho, mesmo quando o serviço não esteja acessível.

Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.