Mensageria: Comunicação Assíncrona Entre Aplicações com RabbitMQ

O que é mensageria? Mensageria é uma abordagem de comunicação que permite a troca de mensagens entre partes de um sistema distribuído. Nesse contexto, as mensagens são gerenciadas por um message broker (corretor de mensagens).

O que é mensageria?

Mensageria é uma abordagem de comunicação que permite a troca de mensagens entre partes de um sistema distribuído. Nesse contexto, as mensagens são gerenciadas por um message broker (corretor de mensagens).

Mensagem

Em uma mensagem está o conteúdo a ser compartilhado, podendo assumir diversos formatos, como JSON, XML, texto simples e dados binários.

Sistema distribuído

Sistema distribuído é uma coleção de elementos computacionais independentes que trabalham de modo colaborativo, mas que se apresenta como um sistema único e coerente para seus usuários. Exemplificando, uma arquitetura de microsserviços representa um tipo de sistema distribuído.

Message broker

Um message broker é um software capaz de lidar com o armazenamento de mensagens e possibilitar que elas sejam entregues aos destinatários apropriados ou descartadas. Atuando como um intermediário entre aplicações, o message broker permite que os remetentes publiquem mensagens sem a necessidade de se preocupar com a disponibilidade dos destinatários e geralmente funciona como uma estrutura de fila.

Resumidamente, um message broker é composto por:

  • producers: remetentes das mensagens enviadas para o message broker.
  • consumers: destinatários inscritos nas filas que possuem interesse em consumir mensagens.
  • queues: estrutura de dados que armazena as mensagens enviadas pelos producers e posteriormente consumidas pelos consumers. Uma fila pode ter diversos atributos, como nome, durabilidade, exclusão automática, argumentos, etc.
  • exchanges: entidades responsáveis por direcionar as mensagens para as filas adequadas. Também podem ter diversos atributos, como nome, durabilidade, exclusão automática, argumentos, etc. Os tipos mais comuns de exchanges são: 
    • direct exchange – utiliza uma chave de roteamento (routing key) para corresponder a uma chave de ligação (binding key) de uma ou mais filas.
    • fanout exchange – distribui mensagens para todas as filas vinculadas a ela e ignora a chave de roteamento (routing key).
    • topic exchange – utiliza padrão de correspondência (palavras delimitadas por pontos) definido na chave de roteamento (routing key) e na chave de ligação (binding key).
    • headers exchange – se baseia em critérios nos cabeçalhos das mensagens.
  • bindings: servem para realizar a ligação entre as exchanges e as filas.

Na ilustração abaixo, temos uma representação de um message broker intermediando a comunicação entre duas aplicações. A ideia é que a aplicação A (producer) envie uma mensagem para o message broker. O message broker terá uma exchange para receber a mensagem e direcioná-la para a(s) fila(s) adequada(s) com base na correspondência com os bindings. Dado isso, a Aplicação B (consumer) deve estar devidamente configurada para consumir as mensagens da fila onde a mensagem foi publicada e processá-las de acordo com a necessidade.

Quando utilizar mensageria

Os principais pontos que podemos levar em consideração quando pensamos na implementação de mensageria são:

  • Comunicação assíncrona – a aplicação produtora pode enviar mensagens sem se preocupar com a disponibilidade imediata da aplicação consumidora.
  • Desacoplamento – a aplicação produtora das mensagens não é afetada em caso de manutenção ou falha na aplicação consumidora.
  • Resiliência – possibilidade de devolver a mensagem para a fila e processá-la novamente em caso de falhas no consumo. 
  • Escalabilidade – quando a demanda aumenta, é possível incluir mais consumidores em uma fila, dividindo a carga de trabalho.
  • Processamento em segundo plano (background) – útil em situações que envolvem a execução de tarefas demoradas.

RabbitMQ

RabbitMQ é uma solução de mensageria de código aberto (open source) que adota o protocolo AMQP (Advanced Message Queuing Protocol) para permitir a comunicação assíncrona entre sistemas de mensagens. Atuando como um message broker, ele fornece uma plataforma eficiente e confiável para facilitar a interação entre os componentes de um sistema distribuído, atendendo a requisitos de alta escalabilidade e disponibilidade. Além disso, também é compatível com diversas linguagens de programação.

Exemplo introdutório com Ruby e RabbitMQ

Faremos uma implementação simples de duas classes Ruby, uma representando a aplicação produtora de mensagens e outra representando a aplicação consumidora. O RabbitMQ será utilizado para intermediar a troca das mensagens.

Primeiro, iremos instalar/configurar o RabbitMQ, para isso podemos consultar sua documentação no site oficial.

Após a instalação, teremos acesso a interface gráfica através da porta 15672 (http://localhost:15672/):

Na aba Queues vamos adicionar uma fila chamada notifications:

Em seguida, iremos configurar os bindings da fila informando a exchange que será vinculada a ela (neste exemplo usaremos a direct exchange, criada por padrão) e uma routing key chamada generic para possibilitar a entrega das mensagens na fila correta:

As exchanges podem ser visualizadas na aba Exchanges, todas essas que estamos vendo são criadas por padrão:

Agora, vamos começar com a implementação da aplicação produtora criando um diretório producer_application:

mkdir producer_application

Dentro do diretório producer_application, iremos adicionar um arquivo Gemfile para gerenciar as gems que serão utilizadas, a princípio apenas uma é necessária:

source "https://rubygems.org"

gem "bunny"

A gem bunny é uma biblioteca para Ruby que fornece uma API para interação com o  RabbitMQ.

E também vamos adicionar a classe BrokerClientProducer, que será responsável por lidar com a publicação de mensagens no broker (RabbitMQ). As mensagens serão publicadas em formato JSON:

require "json"
require "bunny"

class BrokerClientProducer
  def initialize
    @connection = Bunny.new.start # ~> criando uma conexão com o servidor RabbitMQ na máquina local com todas as configurações padrão
    @channel = @connection.create_channel # ~> um canal é como um caminho de comunicação separado dentro de uma conexão
  end

  def publish(message:, routing_key:)
    exchange = @channel.direct("amq.direct") # ~> atribuindo a direct exchange para recebimento das mensagens que serão publicadas

    exchange.publish(
      message.to_json,
      routing_key: routing_key # ~> a routing key da direct exchange precisa corresponder a alguma binding key para a mensagem ser enfileirada
    )
  end
end

A seguir, vamos acessar o diretório producer_application em um terminal para instalar as dependências adicionadas no Gemfile:

bundle install

Agora, daremos início a implementação da aplicação consumidora criando um diretório consumer_application:

mkdir consumer_application

Dentro do diretório consumer_application, iremos adicionar um arquivo Gemfile com o mesmo conteúdo do que está na aplicação produtora:

source "https://rubygems.org"

gem "bunny"

E também vamos adicionar uma classe BrokerClientConsumer, esta será responsável por lidar com o consumo das mensagens:

require "json"
require "bunny"

class BrokerClientConsumer
  def initialize
    @connection = Bunny.new.start # ~> criando uma conexão com o servidor RabbitMQ na máquina local com todas as configurações padrão
    @channel = @connection.create_channel # ~> um canal é como um caminho de comunicação separado dentro de uma conexão
  end
  
  def consume(queue_name:, binding_key:)
    queue = @channel.queue(queue_name, durable: true) # ~> definindo a fila de onde as mensagens serão consumidas
    exchange = @channel.direct("amq.direct") # ~> 
    queue.bind(exchange, routing_key: binding_key) # ~> vinculando uma fila a uma exchange do tipo direct e a 
                                                   #    binding key para fazer correspondência com a routing key da exchange

    begin
      queue.subscribe(manual_ack: true, block: true) do |delivery_info, _properties, body|
        message = JSON.parse(body)
        puts "Received message: #{message}" # ~> este é só um exemplo de tratamento da mensagem

        @channel.ack(delivery_info.delivery_tag) # ~> com a flag manual_ack podemos enviar uma confirmação de consumo 
                                                 #    da mensagem, dessa forma, ela poderá ser apagada da fila.
                                                 #    delivery_info contém informações sobre a entrega da mensagem e
                                                 #    delivery_tag é um identificador da entrega
      end
    rescue Interrupt => _
      @channel.close # ~> fechando o canal em caso de exceção
      @connection.close # ~> fechando a conexão em caso de exceção
    end
  end
end

A seguir, vamos acessar o diretório consumer_application em um terminal para instalar as dependências adicionadas no Gemfile:

bundle install

Agora iremos testar!
Vamos abrir um terminal irb no diretório consumer_application para consumir as mensagens:

irb

A aplicação consumidora ficará aguardando as mensagens da fila notifications, generic é a key definida para que as mensagens sejam enfileiradas:

Em seguida, iremos abrir um terminal irb no diretório producer_application para publicar uma mensagem:

irb

A mensagem deve ser publicada com a key generic:

No terminal onde estamos aguardando o consumo, a mensagem foi recebida e impressa:

Na interface do RabbitMQ é possível visualizar que ocorreu tanto a publicação da mensagem como a confirmação do consumo:

Para finalizar, também podemos visualizar as conexões abertas na aba Connections, sendo uma aberta pela aplicação produtora e outra pela aplicação consumidora:

Os canais abertos também podem ser visualizados na aba Channels:

E assim finalizamos nosso exemplo. =)

Conclusão

A mensageria pode desempenhar um papel crucial na arquitetura de muitos sistemas. O RabbitMQ é uma ferramenta poderosa, com diversos recursos avançados para cenários de uso complexos. Ainda há muitos outros tópicos nos quais podemos nos aprofundar, desde tratamento de mensagens que não podem ser entregues, propriedades das mensagens, distribuição de mensagens entre múltiplos consumidores, durabilidade de mensagens e filas, fechamento de conexões, autenticação e autorização, entre outros. Esse é apenas um começo de como as coisas funcionam.

Referências

  • https://www.alura.com.br/videos/o-que-e-mensageria–c1136
  • TANENBAUM, Andrew S.; VAN STEEN, Maarten. Sistemas Distribuídos: Princípios e Paradigmas. 2ª edição. São Paulo: Pearson, 2007. Capítulo 1, “Introdução,” páginas 1-18.
  • https://www.ibm.com/br-pt/topics/message-brokers
  • https://www.rabbitmq.com/
  • https://www.rabbitmq.com/tutorials/tutorial-one-ruby.html
  • https://www.rabbitmq.com/tutorials/amqp-concepts.html
  • https://medium.com/@ranjeetvimal/message-queues-10-reasons-to-use-message-queuing-1923277a2e7f
0 Comentário