Construindo serviços assíncronos em Go com o NATS

Rafael Souza
Único
Published in
8 min readSep 25, 2020
Fonte: Unsplash

Há alguns anos, quando iniciamos a construção da nossa plataforma de admissão digital, o AcessoRH, imaginamos uma arquitetura de microsserviços com dois tipos possíveis de comunicação entre eles:

  • Chamadas RPC para casos onde fosse necessário uma resposta imediata, como ao criar uma nova vaga ou listar as já existentes (estamos em processo de troca dessas chamadas para gRPC e devo escrever sobre isso em breve);
  • Publicando uma mensagem em uma fila, que será processada mais tarde por um ou mais serviços, como no envio de uma notificação ou na indexação de um novo candidato no nosso motor de busca.

Esta segunda forma de comunicação se dá de uma forma assíncrona, ou seja, o serviço que publicou nessa fila não espera a resposta do serviço que está ouvindo ela. Esse sistema de comunicação recebe o nome de pub/sub (publisher e subscriber) e é possível graças aos mais diversos sistemas de mensagens existentes no mercado, como o Kafka, RabbitMQ, NSQ, entre tantos outros.

Aqui, no AcessoRH, escolhemos o NATS.

NATS

O NATS é um sistema de mensagens open source desenvolvido em Go, mantido pela CNCF e que se destaca por ser extremamente leve e performático.

(Chart source: bravenewgeek.com/dissecting-message-queues)

Ele é capaz de lidar com 3 modelos de distribuição de mensagens: pub/sub, request-reply e queue groups. Vamos dar uma passada rápida por cada uma delas.

Pub/Sub

A forma mais simples de comunicação através de message system. Um primeiro serviço publica uma mensagem em um tópico (publisher) e os serviços ativos que ouvem esse mesmo tópico a recebem (subscribers). Esse modelo de distribuição é conhecido como one-to-many (um para muitos).

https://docs.nats.io/nats-concepts/pubsub

Request-Reply

Uma forma de comunicação que substitui as chamadas síncronas tradicionais como o RPC e o gRPC. O primeiro serviço publica um evento em uma fila de requisições e aguarda a resposta de um segundo serviço em uma fila de respostas.

https://docs.nats.io/nats-concepts/reqreply

Queue Groups

Esse aqui é parecido com o Pub/Sub. Agora, os subscribers além de decidirem quais tópicos ouvem, eles também escolhem um nome para sua fila e, quando mais de um subscriber escolhe o mesmo nome, eles formam um grupo. Assim, quando um serviço publica uma nova mensagem em um tópico, apenas um dos serviços do grupo que o ouve recebe esta mensagem. Caso existam mais de um grupo ouvindo o mesmo tópico, a mensagem será enviada para ambos e será processada por um serviço de cada.

https://docs.nats.io/nats-concepts/queue

No AcessoRH, utilizamos este método para distribuirmos nossas mensagens entre os serviços que configuram nossa plataforma, já que uma ação pode resultar em diversas outras e, pra cada uma delas, um serviço específico é responsável.

Agora chega de conversa e vamos montar nossos publishers e subscribers.

Instalando o NATS Server

Primeiro, precisamos instalar o NATS Server. Existem várias formas de se fazer isso. Você pode encontrar todas elas aqui: https://docs.nats.io/nats-server/installation.

Para esse exemplo, vamos usar o Docker pra facilitar ainda mais a nossa vida. Caso você ainda não o tenha instalado, clique aqui e siga os passos que eu te espero.

Prontx? Então, vamos lá.

Com o Docker instalado, abra qualquer terminal e execute:

docker pull nats:latest

Se tudo correu bem, o Docker baixou a última versão do NATS Server. Agora, execute:

docker run -p 4222:4222 -ti nats:latest

Em seguida, vamos usar o go get para baixarmos o client e conseguirmos conectar nossas aplicações.

go get github.com/nats-io/go-nats

Publisher

Como queremos fazer uma comunicação entre serviços, primeiro vamos definir qual mensagem queremos passar:

Seguiremos com essa estrutura bem simples para realizarmos a comunicação entre os serviços neste exemplo.

Agora vamos construir o serviço que vai publicar nossa primeira mensagem:

Primeiro realizamos a conexão com o NATS Server, utilizando o método Connect do client. Utilizamos aqui o valor padrão da URL definida pelo próprio client: nats://127.0.0.1:4222. Caso seu servidor esteja em outro endereço, basta inseri-lo como uma simples string.

Em seguida, usamos um for, que se repetirá a cada 2 segundos, para que nosso exemplo se torne mais visual e possamos perceber o envio de várias mensagens aos nossos subscribers e como eles se comportam.

O NATS possibilita o envio de nossas mensagens como um array de bytes, por isso precisaremos transformar nossa estrutura Event para este formato. Aqui, seguiremos o caminho do JSON. Um simples json.Marshal é o suficiente para que possamos seguir e já publicarmos nossos eventos no tópico event.

Subscriber

Como comentei alguns tópicos acima, o NATS permite três modelos de distribuição de mensagens distintos. Vamos cobrir dois deles.

Comecemos com o Pub/Sub. Basta nos conectarmos ao Server e nos inscrevermos no mesmo tópico onde as mensagens estão sendo publicadas:

Perceba que quando utilizamos o método Subscribe, precisamos passar uma função que será responsável por tratar as mensagens recebidas. No nosso caso é uma função simples que apenas faz o Unmarshal do JSON para nossa estrutura de eventos e, em seguida, exibe o resultado na tela.

A função runtime.Goexit() previne nossa aplicação de ser finalizada, possibilitando que ela possa receber todas as mensagens publicadas no tópico event enquanto segue ativa.

Abaixo, podemos ver o resultado de um publisher e dois subscribers sendo executados:

Pub/Sub

Perceba que para cada mensagem publicada, ambos os subscribers a recebem e a processam.

Agora vamos usar o método de distribuição Queue Groups para que apenas um deles receba a mensagem:

Pouca coisa mudou. O método Subscribe foi trocado pelo QueueSubscribe e, com isso, foi adicionado mais um parâmetro. Este novo parâmetro indica o nome da fila que nosso Subscriber vai criar ou, se caso ela já exista, se unir ao grupo.

Com essa simples alteração, quando executamos novamente o publisher e dois subscribers:

Queue Group

Agora apenas um dos subscribers recebe a mensagem publicada e isso acontece de forma aleatória. É o NATS trabalhando como um Load Balancer, balanceando a carga entre os dois serviços do mesmo grupo. Caso inseríssemos um segundo grupo no exemplo, um serviço do grupo A e um serviço do grupo B receberiam a mesma mensagem.

NATS Streaming

Caso você esteja se perguntando:

Tá! Muito legal, mas e se meus subscribers caírem? Eu vou perder as mensagens publicadas enquanto eles estiverem fora?

Sim, vai.

O NATS é um sistema de mensagens que serve como comunicação entre microsserviços de forma assíncrona. Ou seja, quem enviou a mensagem não espera pela resposta imediata. Caso o serviço que deveria processar a mensagem esteja indisponível, esta mensagem se perderá.

Mas calma! Não se desespere.

A equipe do NATS também pensou nas pessoas que precisam que estas mensagens não sejam simplesmente descartadas quando não puderem ser processadas. É o nosso caso aqui no AcessoRH.

Para resolver nossos problemas, usamos o NATS Streaming. Ele proporciona persistência para todas as mensagens, seja guardando-as na memória, em arquivos ou em um banco de dados. Com ele também surge o conceito de At least once delivery, que basicamente significa que uma mensagem será entregue ao menos uma vez para um subscriber.

Para entregar tudo isso, o NATS Streaming nada mais é que um client para o próprio NATS, se comunicando através de APIs para gravar as mensagens processadas pelo NATS.

NATS Streaming

Para controlar as mensagens recebidas, o NATS Streaming te dá algumas opções: iniciar o processamento de todas as mensagens novamente, iniciar o processamento das mensagens a partir de uma específica ou continuar o processamento a partir da última mensagem processada. Para esta última ele utiliza um conceito chamado durable, onde cada subscriber precisa escolher um nome para seu próprio storage que será gerenciado pelo NATS Streaming.

Confuso? Vamos construir um exemplo para que isso fique mais claro.

Primeiro, vamos baixar a imagem do NATS Streaming com o Docker, assim como fizemos com o NATS:

docker pull nats-streaming:latest

Caso queira instalar de outra forma, você pode seguir estas instruções: https://docs.nats.io/nats-streaming-server/install

Agora, vamos executar:

docker run -p 4222:4222 -ti nats-streaming:latest -st FILE --dir /data/stan

O parâmetro -st indica que queremos que o NATS Streaming armazene nossas mensagens em arquivos e o --dir aponta o diretório de armazenamento.

Vamos novamente construir o publisher e o subscriber, mas agora utilizando o client do NATS Streaming:

go get github.com/nats-io/stan.go

Algumas coisas mudaram no nosso publisher. Podemos perceber que a forma de conexão mudou um pouco. Agora devemos apontar em qual cluster queremos conectar (test-cluster é o nome do cluster padrão formado quando subimos o NATS Streaming), além de darmos um nome único para cada client conectado.

No subscriber, além de termos que apontar um nome de cluster diferente na momento da conexão, também precisamos indicar um nome para o durable com a função stan.DurableName. Este passo é necessário apenas se quiser utilizar um durable como controle no recebimento das suas mensagens.

Para testar nossos exemplos, executei o publisher e o subscriber, derrubei o subscriber por um tempo e depois o executei novamente. Quando nosso subscriber volta a funcionar, ele recebe todas as mensagens publicadas enquanto ele estava inoperante.

Durable

É dessa forma que garantimos que todas as notificações do AcessoRH serão entregues aos seus destinatários. Não só mantendo nossas mensagens salvas com o NATS Streaming, como também controlando quando essas mensagens são processadas com sucesso (se quiser sabe mais sobre isso dá uma olhada aqui).

E é isso! Desculpe por ter me alongado demais, mas espero que esse artigo tenha sido útil pra você que está começando a descobrir esse mundo de message systems e tenha o NATS como uma das suas opções de escolha.

Ah, caso você tenha se interessado por esse conteúdo e seja apaixonado por tecnologia, a Acesso Digital está contratando engenheir@s. Basta conferir as vagas aqui. 😄 🚀

Rafael Souza é desenvolvedor na Acesso Digital.

--

--