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.
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.
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.
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)