DEV Community

Samuel Pires
Samuel Pires

Posted on

Filas e Microserviços, como e quando implementar.

Quando estamos começando no mundo do desenvolvimento backend, logo percebemos que nem tudo é um grande CRUD. Principalmente para atravessar a ponte entre o nível Júnior e Pleno, precisamos aprender alguns conceitos novos — e um deles são os "Microserviços" e as "Filas".

Um problema que notei é que boa parte do conteúdo sobre esse assunto, muitas vezes, utiliza jargões difíceis que podem acabar limitando o aprendizado de alguém que nunca teve contato com esse tipo de material.

Para mim, por muito tempo, microserviços nada mais eram do que simplesmente “serviços pequenos” (seja lá o que isso quisesse dizer). Mensageria? Eu não fazia ideia. Kafka? Só conhecia o escritor...

Para começar a desmistificar esses assuntos, vamos primeiro entender quando microserviços e filas são necessários.

Imagine que você tem um grande sistema de API para um banco — um sistema responsável por TUDO o que o banco faz, escalando infinitamente. Como no print abaixo.

Image description

Processamento, onde os microserviços começam a fazer sentido.

Agora, imagine que todo tipo de transação dentro dessa API está fadada a competir por processamento com todos os outros recursos do sistema. Para fazer um depósito, eu preciso "disputar espaço" com outro usuário tentando solicitar um empréstimo — e o meu depósito só será processado depois que o empréstimo dele for concluído, e assim por diante.

Se o seu banco tem, talvez, mil clientes, pode até ser que tudo funcione bem. Mas e quando esses números crescerem? Um grande número de pessoas tentando fazer depósitos pode afetar diretamente a experiência de um único usuário que só quer fazer uma transferência.

Ou algo pior pode acontecer: imagine que algo muito sério aconteça e todo o seu sistema seja derrubado. Agora, nenhum cliente consegue acessar nada — nem fazer depósitos, nem empréstimos. O banco, literalmente, caiu por completo.

Para resolver esse problema em um sistema monolítico, seríamos obrigados a escalar o sistema como um todo para ganhar mais performance. Mas... e se existisse uma forma de escalar apenas um módulo específico sob demanda?
É aí que os microserviços se tornam realmente úteis.

A arquitetura de microserviços consiste em termos todos os módulos do nosso sistema desacoplados uns dos outros. Nesse caso, deixamos de ter uma única API com todos os endpoints possíveis, e passamos a ter várias "micro" APIs, cada uma responsável por um módulo individual e hospedada de forma independente.

Dito isso, poderiamos ter algo parecido com o print abaixo.

Image description

No modelo representado acima, nossos sistemas estariam desacoplados e independentes uns dos outros. Uma instabilidade no sistema de pagamentos não geraria conflito no sistema de empréstimos, e vice-versa.

O papel das filas

Filas trabalham lado a lado com microserviços, e o motivo disso é que, agora, como temos múltiplos serviços desacoplados, precisamos de um meio sólido para manter a consistência dos dados comunicados entre um e outro.

É completamente possível ter microserviços se comunicando via HTTP normalmente (ou por algum outro protocolo interno). Porém, isso pode ser um problema devido à concorrência entre requisições, e à chamada "race condition" — uma vulnerabilidade já bem conhecida que pode causar problemas, principalmente em sistemas com alto volume de leitura e escrita de dados.

Sobre race condition

Suponhamos que um cliente tente fazer uma transferência para outro várias vezes e tenhamos a seguinte situação:

  • Cliente A possui R$ 40,00 em conta.

  • Cliente A paga R$ 30,00 para o Cliente B.

  • Cliente A paga R$ 30,00 para o Cliente B novamente.

O que deveria acontecer?

A primeira transferência leria o saldo atual de A (R$ 40,00) e seria realizada normalmente.
Como estamos trabalhando com microserviços, o serviço de pagamentos receberia essa ordem e iniciaria o processo de pagamento. Porém, devido à latência do banco de dados, a transferência demora mais do que deveria — e o Cliente A, vendo que seu saldo não foi alterado, tenta realizar o pagamento novamente.

E quando menos se espera, o Cliente A se encontra com –R$ 20,00, e o Cliente B com R$ 60,00.

Uma situação completamente errada e inesperada.

Mas como isso aconteceu?

Aqui temos um caso do que chamamos de race condition, onde duas requisições quase simultâneas leem o mesmo dado no mesmo estado, ao mesmo tempo.

Como a primeira tentativa de pagamento estava com atraso, a segunda tentativa leu o banco de dados antes que a primeira fosse concluída.
Portanto, ela entendeu que o Cliente A ainda tinha os R$ 40,00 de saldo original — e enviou uma segunda ordem de pagamento.

E se tivéssemos uma forma de enfileirar as requisições e tornar o processo totalmente síncrono, garantindo que uma ordem so seria processada assim que outra ordem anterior fosse totalmente completa?

Conseguimos fazer isso implementando um sistema de filas/mensageria, voce ja deve ter ouvido nomes muito famosos como RabbitMQ ou Apache Kafka, ou Amazon SQS.

Como filas e mensagerias funcionam

Os modelos mais comuns de sistemas de filas seguem o padrão "produtor e consumidor".

Mas o que isso significa?

Basicamente, um sistema "produz" uma mensagem em uma fila, contendo qualquer dado necessário (por exemplo, um JSON), e outro sistema "consome" essa mensagem, retirando-a da fila e processando o conteúdo, ou em casos de erro, retornando a mesma para o final da fila original (isso e opcional de sistema para sistema).

Solucionando o problema

Veja como no exemplo abaixo da situacao que descrevemos.

Image description

Aqui, não teríamos o problema de dois pagamentos sendo processados ao mesmo tempo, já que o segundo seria, obrigatoriamente, processado apenas após o término do primeiro.

Isso permite que você controle a frequência do fluxo de leitura de pagamentos, evitando sobrecarga no sistema e mantendo a consistência total dos dados.

Sistemas modernos de filas permitem configurar múltiplas filas para diferentes "assuntos", possibilitando o processamento simultâneo de dados não relacionados, sem conflitos. Eles também permitem ajustar o tempo e a forma como cada processamento deve ocorrer. Às vezes, você pode querer ler 10 mensagens ao mesmo tempo ou executar leituras agendadas em lotes. Tudo isso é configurável nos sistemas de filas mais populares.

Como vimos, microserviços e filas não são conceitos místicos nem “coisas de empresa grande” — são ferramentas que ajudam a manter nossos sistemas mais organizados, performáticos e preparados para crescer.

Aprender isso é uma das chaves para sair do modo CRUD e começar a pensar como engenheiro de software: buscando soluções escaláveis e resilientes.

Se você chegou até aqui, espero que esse conteúdo tenha te ajudado a enxergar esse mundo com menos mistério e mais clareza.

Top comments (0)