Previsão: nublado

Encerrando a viagem com consultas do AppFabric

Joseph Fultz

Baixar o código de exemplo

Joseph Fultz
Na edição de outubro, falei sobre alguns dos novos recursos do Barramento de Serviço do Windows Azure AppFabric (msdn.microsoft.com/magazine/hh456395). Este mês, continuarei com o cenário com o qual comecei, fazendo a viagem de volta. Até agora, no cenário, uma loja solicitou uma verificação de estoque das lojas próximas publicando solicitações de verificação de estoque em um Tópico. As lojas com assinaturas do Tópico receberam as solicitações com base em um Filtro da assinatura que limitava as mensagens àquelas dentro de suas regiões e não enviadas por elas.

Na viagem de volta, farei uso de dois recursos das Filas do Barramento de Serviço do Windows Azure AppFabric. Em primeiro lugar, existem duas propriedades importantes para as quais atribuirei valores na BrokeredMessage que é enviada ao Tópico. A primeira propriedade à qual atribuirei valor é a propriedade ReplyTo, para dizer ao destinatário para onde ele deve enviar a mensagem. Essa será uma Consulta específica criada pelo remetente no momento em que ele envia a mensagem. Quero usar uma Consulta em vez de um Tópico nesse caso porque a resposta irá para um destinatário, ao contrário do padrão de solicitação, que é equacionado em uma difusão para ajudar a todos os que estão ouvindo.

A nova parte do fluxo da mensagem é mostrada na Figura 1, no fluxo para a direita da linha de lojas.

Inquiry and Response Round-Trip
Figura 1 Viagem de ida e volta de consulta e resposta

Atualizando o código da solicitação de saída

Na primeira passada, enviei a mensagem para o Tópico e demonstrei sua retirada pelas assinaturas apropriadas, mas dois itens importantes ficaram de fora, em prol do suporte a uma resposta simples. O primeiro item é a configuração da propriedade CorrelationId da BrokeredMessage. A propriedade faz parte da API, portanto, eu não preciso incluí-la como parte da minha classe de dados ou do esquema. Por ser uma cadeia de caracteres, ela pode ser algo que faça sentido, mas deve identificar de forma exclusiva a mensagem para que nenhuma resposta que tenha uma CorrelationId correspondente seja confundida como correspondente a outra solicitação do sistema. Para as minhas finalidades de exemplificação, eu uso uma GUID, mas isso também é muito seguro na prática. Eis o código:

BrokeredMessage msg = BrokeredMessage.CreateMessage(data);
// Add props to message for filtering
msg.Properties["Region"] = data.Region;
msg.Properties["RequesterID"] = data.RequesterID;
// Set properties for message
msg.TimeToLive = TimeSpan.FromSeconds(30);
msg.ReplyTo = returnQueueName;
msg.CorrelationId = Guid.NewGuid().ToString();

A BrokeredMessage também tem uma propriedade chamada SessionId, que não está sendo usada aqui. SessionId é mais uma propriedade para agrupamento lógico aconselhável no nível de envelope da mensagem, porque ela facilita o agrupamento de mensagens relacionadas. A questão é como ela difere, em intenção, de CorrelationId. Existem dois cenários nos quais SessionId pode ser particularmente útil. O primeiro é um sistema no qual vários daemons estão criando várias solicitações, todas relacionadas. CorrelationId seria usada para rotear a resposta para o processador solicitante. SessionId seria usada para agrupar todas as mensagens enviadas e recebidas pelos nós de processamento. Esse agrupamento é útil para se determinar o estado do processamento em um sistema para análise e depuração. Pelos mesmos motivos, essa é uma construção útil em um sistema que faz muitas solicitações como parte de um processo geral (por exemplo, processo de compras, verificação de estoque, verificação de pagamentos, envio para abastecimento, etc.), mas no qual o fluxo e o tempo exatos não são garantidos.

Após definir CorrelationId na mensagem de saída, a próxima alteração que preciso fazer é definir a propriedade ReplyTo. Eu posso criar outro Tópico e fazer com que todas as lojas monitorem as respostas ou usar uma única fila, mas isso geraria um tráfego desnecessário e, em períodos de pico, teria mais probabilidade de causar um gargalo. Portanto, faz sentido simplesmente criar uma fila de respostas no momento da solicitação e informar o destinatário sobre sua localização. Por ser uma cadeia de caracteres, ela pode ser qualquer coisa, embora eu recomende um nome totalmente qualificado para evitar qualquer confusão ou conflito em futuras evoluções do software. Você pode começar com apenas o nome da fila, mas em casos de manutenção e expansão, isso pode gerar confusão quando o sistema começar a dar suporte a vários namespaces de barramento de serviço e subfilas. Além disso, um endereço totalmente qualificado será melhor para os destinatários que não usam o Microsoft .NET Framework.

As últimas duas coisas a fazer antes de enviar a mensagem é criar a fila de respostas e iniciar um timer para verificar as respostas. Eu comecei com o método GetQueue. No momento da elaboração deste artigo, a documentação (bit.ly/pnByFw) do GetQueue afirmava que, para o “Valor de Retorno”, “tipo” é igual a Microsoft.ServiceBus.Messaging.Queue; um identificador para a fila; ou nulo, se a fila não existir no namespace do serviço. Mas esse não é o caso. Na verdade, usarei uma exceção:

// Check if-exists and create response queue
try
{
  returnQ = this.ServiceBusNSClient.GetQueue(returnQueueName);
}
catch (System.Exception ex)
{
  Debug.WriteLine(ex.Message);
}
if (returnQ == null)
{
  returnQ = this.ServiceBusNSClient.CreateQueue(returnQueueName);
}
checkQTimer.Enabled = true;

Portanto, encapsulei o método GetQueue em um bloco try-catch e segui em frente. Meu código de exemplo, como sempre, não faz nada mais do que escrever o erro. Depois que a fila é criada, eu a atribuo a uma variável para que possa fazer referência a ela para verificar a fila e habilito o timer que configurei no início do aplicativo.

Atualizando o destinatário e respondendo

Com as modificações apropriadas no lado do remetente, tenho que fazer algumas adições para que possa responder às solicitações. Vou usar uma resposta inserida no código, mas listarei as mensagens em uma grade para que possa selecionar aquela à qual responderei. Eu configurei um mecanismo de evento para notificar a interface do usuário sobre novas mensagens. Depois que uma mensagem é recebida, eu defino a ID de armazenamento do destinatário atual como respondente e notifico a interface do usuário sobre a mensagem para exibição:

recvSuccess = msgReceiver.TryReceive(TimeSpan.FromSeconds(3), out NewMsg);
if (recvSuccess)
{
  CreateInquiry.InventoryQueryData qryData = 
    NewMsg.GetBody<CreateInquiry.InventoryQueryData>();
  NewMsg.Properties.Add("ResponderId", this.StoreID);
  EventContainer.RaiseMessageReceived(NewMsg);
}

Dentro da interface do usuário, assinei o evento que receberá a mensagem e o passei para um método para atualizar os elementos da interface do usuário que farão a verificação necessária do método InvokeRequired:

void EventContainer_MessageReceivedEvent(object sender, MessagingEventArgs e)
{
  BrokeredMessage msg = (BrokeredMessage)e.MessageData;
  var RequestObject =
    msg.GetBody<CreateInquiry.InventoryQueryData>();
  RequestObject.ResponderID = msg.Properties["ResponderId"].ToString();
  this.ReceivedMessages.Add(RequestObject);
  UpdateUIElements(msg.MessageId, RequestObject);
}

Com o código completo para buscar as mensagens para o Tópico e atualizar a interface do usuário, agora posso visualizar as mensagens recebidas (veja a Figura 2).

The Inventory Topic Monitor
Figura 2 O monitor de tópicos sobre estoque

Essa interface do usuário (não fui contratado por minhas habilidades de design de experiência do usuário, obviamente) me permitirá selecionar uma das mensagens e respondê-la com uma quantidade que é definida na caixa de texto inferior. Elaborar o código da resposta será uma tarefa bastante simples e rápida, já que tenho apenas que adicionar um breve código para definir a quantidade no objeto da mensagem e, depois, enviá-la para a Fila especificada na propriedade ReplyTo da mensagem.

Neste exemplo, estou simplesmente adicionando mensagens recebidas do Tópico a um objeto Dictionary<string, BrokeredMessage>, onde estou usando BrokeredMessage.MessageId como a chave do dicionário. Basta usar a ID da mensagem da grade para recuperá-la do dicionário e, então, criar uma nova BrokeredMessage atribuindo a mesma CorrelationId e um valor para as propriedades ResponderId e Quantity. Assim como para o recebimento do Tópico, usarei MessagingFactory para criar um QueueClient e, a partir desse objeto, um MessageSender:

// Send message
  SharedSecretCredential credential =
    TransportClientCredentialBase.CreateSharedSecretCredential(
    Constants.issuerName, Constants.issuerKey);
  Uri sbUri = ServiceBusEnvironment.CreateServiceUri(
           "sb", Constants.sbNamespace, String.Empty);
MessagingFactory Factory =  MessagingFactory.Create(sbUri, credential);
QueueClient QClient = Factory.CreateQueueClient(msg.ReplyTo);
MessageSender Sender = QClient.CreateSender();
Sender.Send(ResponseMsg);

Isso envia a resposta de volta, portanto, precisamos mudar nosso foco novamente para o aplicativo de origem a fim de processar a resposta.

Recebendo a resposta

Para este exemplo, quero apenas puxar as respostas da fila. Como o código para enviar a solicitação foi modificado para começar a monitorar a fila ReplyTo, é realmente preciso adicionar o código real para verificar a fila. Começo criando um MessageReceiver e configurando um loop While simples para obter todas as mensagens disponíveis na fila desta vez:

void checkQTimer_Tick(object sender, EventArgs e)
{
  MessageReceiver receiver =
    QClient.CreateReceiver(ReceiveMode.ReceiveAndDelete);
  BrokeredMessage NewMessage = null;
  while (receiver.TryReceive(TimeSpan.FromSeconds(1), out NewMessage))
  {
    InquiryResponse resp = new InquiryResponse();
    resp.CorrelationID = NewMessage.CorrelationId;
    resp.Message = NewMessage;
    InquiryResponses.Add(resp);
  }
 }

Como antes, uso o método TryReceive. Embora ele funcione para este exemplo, eu consideraria uma abordagem um pouco diferente para uma interface do usuário real, porque o método bloqueia o thread, tendo, portanto, uma execução melhor em um thread diferente. Eu quero buscar toda a BrokeredMessage na lista por CorrelationId, portanto, criei um objeto e usei CorrelationId para filtrar o objeto posteriormente. Quero a mensagem como uma BrokeredMessage porque estou interessado nos dados que fazem parte do envelope BrokeredMessage que agora encapsula meu objeto InquiryData (veja a Figura 3).

Inquiry Request and Responses
Figura 3 Solicitação de consulta e respostas

Ao modificar o código SelectedIndexChanged da ListBox, eu simplesmente pego a CorrelationId que usei como item e a utilizo para obter as respostas desejadas da lista que estou criando à medida que as respostas aparecem na fila:

string correlationId =
  lbRequests.SelectedItem.ToString();
List<InquiryResponse> CorrelatedList =
  InquiryResponses.FindAll(
  delegate(InquiryResponse resp)
{
  return resp.CorrelationID == correlationId;
});

A última tarefa é adicionar as respostas a DataGrid para ver os armazenamentos que responderam à minha consulta de estoque. Você notará que, em minha modesta interface do usuário, estou usando GUIDs, mas espero que esteja óbvio para o leitor que elas teriam de ser substituídas por descrições amigáveis, deixando-se as GUIDs e as IDs para as páginas de detalhes.

Revisão

Por ter sido criado dentro do exército, tenho muitos ditados e outras coisas do gênero impressas no meu cérebro. Um exemplo é o “método militar” de educação, que é assim: eu lhe digo o que vamos fazer, nós fazemos e, depois, eu digo exatamente o que era. Bem, eu estou na parte do “o que exatamente nós fizemos”. Eu comecei este artigo como uma entrada única, mas percebi que lidar com a viagem de ida e volta seria demais para uma única coluna. Então, dividi tudo em duas entradas: uma para enviar a solicitação e outra para receber a resposta. Minha meta era apresentar os recursos básicos do Barramento de Serviço do Windows Azure AppFabric e também seu uso em geral. Quando penso em tecnologia, sempre gosto de colocá-la em algum tipo de contexto e, neste caso, eu queria o contexto de uma consulta de produtos entre lojas, porque esse é um cenário viável e porque faz sentido o uso de um misto de Tópicos e Filas.  

No momento da elaboração deste artigo, a CTP de maio do Barramento de Serviço do Windows Azure AppFabric já tinha sido lançada, e algumas informações sobre ela podem ser encontradas em bit.ly/it5Wo2. Além disso, você pode participar dos fóruns em bit.ly/oJyCYx.

Joseph Fultz é arquiteto de software da Hewlett-Packard Co. e trabalha na equipe de TI global do HP.com. Anteriormente, era arquiteto de software na Microsoft, trabalhando com seus clientes empresariais e ISV de camada superior, definindo soluções de arquitetura e design.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Jim Keane