Processamento assíncrono utilizando Go e RabbitMQ. Parte 1
Tags: distributedsystems, go, rabbitmq, microservices
Published: 2020-06-15 02:31:16

Crédito da imagem de capa: [Joanna Kosinska](https://unsplash.com/@joannakosinska?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) no [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)

Algum tempo atrás compartilhei uns artigos falando sobre reprocessamento de mensagens.
Como é algo que acabo utilizando bastante, achei bacana criar um artigo cobrindo a ideia de como e, porque considerar o reprocessamento de mensagens na comunicação entre microsserviços.
Como acabou ficando bastante grande o texto, o conceito da solução será abordado neste texto e a implementação será analisada no próximo artigo.

Escolhi o RabbitMQ para este exemplo por ser um broker de mensageria bastante adotado no mercado, fácil de conseguir rodar localmente e também é uma ferramenta bastante completa.
De qualquer forma, não se prenda a linguagem ou ao broker de mensageria escolhido. Absorva o conceito!
Ao compreender como, quando e, porque utilizar uma determinada solução, você pode replicar com facilidade em praticamente qualquer linguagem de mercado e com algumas variações de ferramentas utilizadas. Por exemplo, poderia ser utilizado ZeroMQ, ActiveMQ, NATS ou até mesmo Kafka no lugar do RabbitMQ.
Embora eu explore algumas poucas features do RabbitMQ, não é intensão deste artigo mostrar o RabbitMQ no detalhe, nem ensinar como configurá-lo para ambiente de produção.

# Entendendo o problema

Vamos imaginar um sistema que foi desenvolvido para uma fábrica de esmaltes que está investindo pesado em automação. Nossa tarefa é melhorar uma parte específica do sistema, entre o controle do estoque de esmaltes e a geração de novas ordens de produção.
O sistema é distribuído entre diversos microsserviços, mas para nossa tarefa, apenas nos interessam dois microsserviços: **Estoque** e **OrdemDeProducao**.

![Imagem mostrando requisição HTTP entre serviço Estoque e OrdemDeProducao](https://dev-to-uploads.s3.amazonaws.com/i/grlcez1fasb8pjca69pj.png)

Quando o estoque de uma determinada cor da coleção vigente está abaixo de 30%, o microsserviço de Estoque automaticamente dispara uma mensagem para o microsserviço OrdemDeProducao, gerando uma ordem de produção para o esmalte em questão.
O serviço se comunica de forma síncrona utilizando requisições HTTP.
E o que acontece se no momento que o Estoque precisar gerar uma ordem de produção, e o microsserviço OrdemDeProducao estiver fora do ar? E se apresentar muita lentidão por algum motivo qualquer, retornando timeout para o microsserviço Estoque?

![Imagem mostrando falha na chamada para OrdemDeProducao](https://dev-to-uploads.s3.amazonaws.com/i/lvy1q2h3by9lwqwt5n8c.png)

# Resolvendo o impacto no lado do microsserviço Estoque

Algo que podemos observar é que esta funcionalidade de gerar uma ordem de produção, não precisa de um retorno imediato. O serviço Estoque apenas precisa gerar a ordem de produção, não faz diferença se isto vai acontecer agora, em 10 minutos ou 30 minutos.
Então podemos entender que esta comunicação não precisa ser síncrona! Podemos resolver esta comunicação utilizando uma fila de mensagens. Dessa forma, o serviço Estoque pode enviar quantas mensagens quiser para a fila, que conforme o serviço OrdemDeServico estiver disponível, ele irá consumir as mensagens da fila para processamento.
Vamos utilizar o RabbitMQ como broker de mensagens.

![Imagem mostrando fila do RabbitMQ controlando chamadas entre os serviços](https://dev-to-uploads.s3.amazonaws.com/i/8b6y2yzl3ykfptjbaca0.png)

Pronto! Primeiro problema resolvido. Agora caso o microsserviço OrdemDeProducao esteja fora do ar ou com muita lentidão, não irá causar impacto para o microsserviço Estoque.
A imagem abaixo mostra que mesmo que o serviço OrdemDeProducao nao consiga processar nada, o Estoque pode continuar disparando mensagens sem qualquer problema.
Quando a OrdemDeProducao voltar a funcionar, as mensagens que estão na fila voltam a ser processadas.

![Imagem mostrando que falhas na OrdemDeProducao não impacta no servico de Estoque](https://dev-to-uploads.s3.amazonaws.com/i/dmwvc3lvva1agdf5uacn.png)

Mas ainda temos outro problema! 
Imagine que o serviço OrdemDeProducao está falhando, porque algum outro serviço está fora do ar. Digamos que seja o banco de dados que não está respondendo.
O que acontece com a mensagem que estamos tentando processar?

Poderíamos jogar novamente a mensagem na fila, para continuar tentando reprocessar!!!
Na verdade, não é bem assim.

Se jogarmos novamente a mensagem para a fila, imediatamente o serviço OrdemDeProducao irá consumir a mensagem e tentar processá-la. Em um cenário que temos muitas mensagens na fila, estaríamos apenas executando à toa repetida e rapidamente sem necessidade.

Aí que entra uma feature bacana do RabbitMQ, chamada [Delayed Messages](https://www.rabbitmq.com/blog/2015/04/16/scheduling-messages-with-rabbitmq/).
Caso o processamento da mensagem falhe, podemos jogá-la novamente na fila, mas informando que ela deve ser processada novamente apenas daqui a alguns minutos.
Dessa forma, antes de tentar reprocessar a mensagem, existe um tempo que podemos aguardar esperando que a falha seja resolvida.

![Imagem mostrando a mensagem com propriedade de delay sendo devolvida para a fila](https://dev-to-uploads.s3.amazonaws.com/i/ymobg8vaj8823hbumo5o.png)

# Dead Letter Queue (DLQ)

O que acontece se ocorrer de muitas mensagens acumularem na fila?
As mensagens excedentes que forem chegando, podem ser diretamente desviadas para uma fila chamada dead-letter-queue (DLQ).

Inclusive, podemos fazer uso de mais outra feature, chamada [Dead-Lettered Effects](https://www.rabbitmq.com/dlx.html#effects). Assim podemos melhorar ainda mais nosso processo de retentativas, adicionando uma quantidade máxima de retentativas no header da mensagem. Caso o número máximo de retentativas seja ultrapassado, podemos desviar a mensagem com falha para aí sim ocorrer a análise manual da situação.
Podemos por exemplo, definir que a quantidade máxima de retentativas será 3 vezes e após estas falhas, a mensagem será desviada para outra DLQ para armazenamento e disparar um alerta para a pessoa responsável analisar manualmente a situação.

Caso o problema tenha sido apenas indisponibilidade de algum serviço e a solução seja apenas reprocessar as mensagens, exite um plugin chamado [Shovel](https://www.rabbitmq.com/shovel.html) que permite jogar todas mensagens de uma fila para outra.

![Solução final utilizando uma fila normal, delayed messages e uma fila DLQ](https://dev-to-uploads.s3.amazonaws.com/i/jgu5qrvfug1k0v4cacsw.png)

## Resumo da solução

- A comunicação era síncrona (HTTP). Ao identificar que ela pode ser assíncrona, passamos a realizar a comunicação utilizando mensageria. 
- Em caso de falha no processamento da mensagem, ela será enviada novamente para a fila com um tempo de delay até a próxima retentativa.
- Definimos uma quantidade máxima de retentativas. Caso seja atingida a quantidade máxima, a mensagem é desviada para uma DLQ, onde aguardará por tratamento manual.

A solução é bem mais complexa do que uma simples requisição HTTP, mas agora conseguimos garantir que mesmo que o serviço OrdemDeProducao esteja com algum problema, ele não irá impactar o Estoque. 
O isolamento de um problema é algo bastante importante que deve ser lembrado ao desenhar uma solução.
Também estamos evitando a análise manual por qualquer tipo de falha, dando a oportunidade para o sistema se recuperar e dar vazão no processamento das mensagens sem intervenção humana.
Neste exemplo do artigo, a intervenção humana só é necessária em casos graves de falha onde a OrdemDeProducao não consegue de forma alguma voltar a processar as mensagens.

Ah, mais uma coisa! Embora o exemplo acima esteja mostrando microsserviços separados para Estoque e OrdemDeProducao, essa solução também pode ser adaptada para processamento assíncrono em projetos monolíticos.

No próxima parte eu irei mostrar a implementação da solução.