Comunicação assíncrona com .NET Core, MassTransit e RabbitMQ
Diego Brunetti
Posted on November 25, 2020
Saudações, developers do meu Brasil! Como estão? Aposto que construindo vários microsserviços sensacionais cheios de comunicação síncrona pra todo lado, certo? Os famigerados monolitos distribuídos. Eles são muito comuns. Em quase todas as empresas que já trabalhei eles estavam lá, praticamente onipresentes.
Se você já possui alguns anos de carreira provavelmente certamente já foi autor ou colaborador no nascimento de um desses monstrinhos, mesmo que contra sua vontade. Decompor um domínio complexo em serviços não é uma tarefa fácil, principalmente quando não temos um conhecimento profundo sobre o mesmo. Arrisco dizer que a maioria dos casos de "fracasso" que já vi são resultado não da incapacidade técnica (as vezes sim), mas de uma quase histeria em construir novos sistemas como microsserviços desde o início. É sempre bom lembrar que: na dúvida, comece com um monolito, extraia os serviços gradualmente.
Vamos a um exemplo fictício: Um service recebe uma request contendo um novo pedido. Para que o pedido seja aprovado e esteja pronto para entrega, precisamos fazer uma análise de crédito e também reservar os itens do pedido no estoque:
No diagrama da Figura 1, vemos que o service A chama os services B e C de maneira síncrona, através de requests HTTP. Essa arquitetura pode até funcionar bem por boa parte do tempo, mas possui um problema sério e levanta várias questões:
O quê acontece com os novos pedidos quando B ou C ficam lentos, totalmente ou parcialmente inoperantes?
Qual o risco de A ser comprometido por alterações estruturais feitas em B ou C, por exemplo, atualizações em contratos das APIs, mudanças de hostname, introdução de bugs, etc?
Como garantir que a latência nas chamadas para a API do serviço A seja baixa na maior parte do tempo independente da saúde dos serviços B e C?
O que acontece se B ou C também possuírem outras dependências e as mesmas param de funcionar? Como garantir que A não seja diretamente afetado fazendo com que novos pedidos sejam perdidos?
O grande problema está na característica síncrona da comunicação entre os serviços A, B, e C, que gera um forte acoplamento entre eles. Os bem intencionados engenheiros responsáveis pelo nosso exemplo fictício vislumbravam uma arquitetura de microsserviços. O resultado, infelizmente, foi um monolito que roda em 3 processos diferentes. Isso é ruim pois muita complexidade foi introduzida em troca de nenhuma vantagem técnica. Logo, o negócio saiu prejudicado.
Em sistemas do mundo real o problema seria muito maior do que está visível no pequeno exemplo acima. No momento em que escrevo este post trabalho em um banco de pequeno/médio porte com aproximadamente 50 microsserviços e o número só cresce sem parar. A gigante Netflix estima possuir mais de 500!
Tente imaginar como seria a sua vida trabalhando dentro desse ecossistema se tudo tivesse sido desenvolvido como os Services A, B e C do nosso cenário hipotético. Pois bem, não seria exatamente um mar de rosas, e se você acha que seria possível desfazer uma bomba dessas tão rapidamente quanto eu escrevo esta sentença, você está profundamente enganado. Por experiência própria posso afirmar com uma boa dose de confiança que uma vez que o seu código toque o ambiente de produção, será tarde demais para fazer alterações de design.
Voltando ao nosso cenário hipotético do início do artigo, acredito que a principal questão a ser respondida aqui é: Como garantir que o service A continue funcionando e aceitando novos pedidos mesmo quando B ou C estão fora do ar? Como garantir que os pedidos não serão perdidos e poderão ser processados quando B e C voltarem a funcionar? Uma das formas é tornar a comunicação assíncrona utilizando troca de mensagens com um message broker como o RabbitMQ. Se um dos services que A depende estiver fora do ar, a mensagem persistida em uma fila poderá ser processada mais tarde. Assim os microsserviços serão autônomos, e continuaremos recebendo novos pedidos normalmente, mesmo se a reserva de estoque ou a análise de crédito estiverem fora do ar.
Só de olhar este diagrama (que só mostra o caminho feliz) podemos notar que tudo tem um preço, já que introduzimos alguns componentes e patterns de comunicação assíncrona. Pra quem desenvolve dentro do ecossistema .NET, existe um excelente framework para ajudar nesse trampo chamado MassTransit. Ele é gratuito, muito bem documentado e possui suporte para vários message brokers (ex: RabbitMQ, Azure ServiceBus, Kafka e AWS SQS).
Eis que finalmente chegamos ao ponto-chave anunciado pelo título deste humilde post. Se você está iniciando na implementação desse tipo de solução, disponibilizei no YouTube uma série de vídeos para te ajudar a implementar comunicação assíncrona em .NET Core usando MassTransit e RabbitMQ. Logo abaixo vocês podem acessar a playlist completa. Espero que gostem 🙂
Referências
A "maldição" dos sistemas monolíticos distribuídos (Elemar Jr)
MonolithFirst (Martin Fowler)
What Are Microservices? (microservices.io)
Microservices: coupling vs autonomy (Geero Vermsas)
Why you can't talk about microservices without mentioning Netflix
Publicado originalmente em https://aresistencia.dev
Posted on November 25, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.