O que é?
Como o próprio nome já diz, um Sistema Distribuído é aquele que tá com suas partes “espalhadas por aí”.
É uma arquitetura onde a gente divide as partes do sistema para cada uma cuidar de um processo ou ponto de entrada.
Se você pensou em micro serviços com o que eu disse é porque eles geralmente são parte de uma arquitetura distribuída mesmo.
Mas eles não são as únicas peças disso; não são só os módulos que são distribuídos, mas os recursos também: desde código e dados até processos e arquivos.
Pra que servem?
Quando uma aplicação cresce e começa a receber muitos acessos e ter vários processos ao mesmo tempo, consequentemente vai ficar mais lento e instável.
E faz sentido: a memória e o processador tem um limite do que conseguem fazer ao mesmo tempo. Então, podem acabar chegando a enfileirar coisa demais pra darem conta ou mesmo demorando pra carregar e executar atividades.
E fica a pergunta: o que a gente faz quando coisas assim acontecem com a gente no dia a dia?
Simples:
- Se a alguém está com muitas tarefas, divide com outras pessoas;
- Se algo está demorado de alcançar, a gente deixa mais perto;
Pra sistemas a lógica é a mesma!
- Se um pedaço do serviço tá consumindo demais do servidor, damos um servidor só pra ele
- Se as imagens estão pesadas demais e estão atrasando o site, colocamos numa CDN
- Se tem uma ação que tá gerando muitas requisições, nós enfileiramos.
Mas é claro que isso gera trade-offs (como tudo) que eu vou falar daqui a pouco.
Mas antes, deixa eu te mostrar como isso é feito.
Como fazer
Pra não gastar muito tempo, vou focar só em distribuição de micro serviços e eventos.
Mas se eu fosse você, eu ficaria de olho que talvez um dos próximos posts seja sobre outras peças importantes.
1. Micro Serviços
A ideia deles não é transformar cada uma das controllers em um serviço a parte, mas sim dividir domínios da aplicação.
Imagina que você tem uma API única pro seu e-commerce, com essas controllers:
- AuthController.cs
- UsersController.cs
- RoleController.cs
- ProductsController.cs
- OrderController.cs
Tudo rodando dentro do mesmo serviço, dentro da mesma API.
Até funciona bem… até chegar a Black Friday.
E na prática, o que mais acontece num e-commerce?
- Cadastro de usuário? Até cresce, mas nem tanto.
- Cadastro de produto? Bem difícil
- Pedidos? Explodem.
Ou seja: numa Black Friday, a parte de pedidos tende a usar muito mais CPU, memória, banco e fila do que o resto do sistema.
Do jeito que está hoje, quando OrderController começa a sofrer vai puxar recurso do mesmo servidor que atende login, cadastro e consulta de produto;
E pode acontecer do usuário nem conseguir fazer login porque tem muita gente fazendo pedidos.
E é aí que faz sentido criar um micro serviço como um jeito de distribuir seu sistema.
Em vez de deixar tudo na mesma API, você vai extrair o domínio de pedidos para um serviço próprio tipo OrdersService que só vai ter endpoints relacionados aos pedidos e vai ter um banco de dados próprio.
Com isso, você leva todo o tráfego para outro lugar, outro servidor, e diminuindo o gargalo.
Claro que isso mudo e muito o fluxo de dados: você não vai poder chamar os outros serviço toda vez que quiser alguma informação e ainda vai precisar duplicar algumas informações.
2. Eventos
Quando você distribui um sistema, você tem que entender que nem tudo vai funcionar como era antes.
Basicamente, o conceito da aplicação muda. Você passa a trabalhar com uma arquitetura mais assíncrona.
Isso quer dizer que algumas chamadas à APIs não vão mais ter o mesmo tipo de resposta que antes .
Numa API monolítica, esse e-commerce provavelmente funcionaria assim: O usuário cria o pedido e o sistema confirma o pagamento, atualiza o estoque e depois ele envia o email confirmando a compra.
Tudo na mesma sequência, dentro do mesmo processo.
Se alguma coisa der errado no meio do caminho, trava as próximas ações e ainda bloqueou um recurso: o item do estoque.
Mas com um sistema distribuído, você pode gerar um Evento publicando uma mensagem em algum serviço como o RabbitMQ.
O OrdersServicesó cria o pedido e avisa: “Pedido criado aqui, se virem.”
A partir desse aviso, cada outro serviço reage no seu próprio tempo.
O de pagamento processa quando recebe, o de estoque atualiza quando conseguir, e o de email dispara a mensagem quando chegar a vez dele.
Agora é mais como vários funcionários ouvindo o mesmo anúncio e cada um fazendo sua parte sem incomodar o outro.
E isso muda toda a experiência da aplicação: nem o usuário não vai ter a resposta na hora.
Mas em compensação, criar um pedido vira uma experiência bem mais rápida, os serviços perdem aquela interdependência e ainda mesmo que algum serviço caia as ações ainda podem ser executadas quando ele voltar ao ar.
Claro, isso traz um efeito colateral inevitável: as coisas nem sempre atualizam na mesma hora.
Você ganha autonomia e resiliência, mas perde um pouco daquela “sincronia perfeita” do monolito.
Mas é o que eu falei: quando você distribui, você muda o jeito que você olha pra sua aplicação como um todo.
Problemas Comuns dessa Arquitetura
Claro que esse modelo tem uns problemas que só existem nele.
E uma das primeiras falhas que você vai ver é a de perda de mensagens; e pode acontecer por N motivos… desde uma falha de rede ou um erro no sistema que tentou processar aquela mensagem.
Pra isso, a maioria das ferramentas de fila tem um mecanismo de Retry, filas de mensagens mortas (dead-letter queues) e persistência pra tratar isso.
Outra coisa que pode (e vai) acontecer é a Duplicação de Mensagens.
É NORMAL você receber a mesma mensagem 2, 3 ou 10 vezes por culpa de um retry com falha ou timeout…
E pra lidar com isso você precisa garantir a Idempotência da mensagem, o que, em outras palavras, significa que você precisa fazer o seu sistema ignorar/tratar as duplicatas para serem processadas uma única vez.
Um jeito interessante de fazer isso é gerar uma “chave idempotente” que pode ser gerada com as informações da mensagem.
Ex.: '{USER_ID}{ORDER_ID}{MESSAGE_TIMESTAMP}' de um jeito que aquela mensagem sempre gere a mesma chave; como um ID.
Assim, você pode ter certeza se aquela mensagem já foi processada ou não.
Outra coisa muito comum que todos sempre querem corrigir é o out-of-order. Cara, como isso incomoda no começo…
Num monolito, você até consegue garantir uma sequência de execução, mas num sistema distribuído, toda tentativa de ordenar os processos não passa de uma “sugestão” pra sua aplicação.
Dependendo do que for seu sistema, a solução pode ser usar versionamento pra nada ser sobrescrito, mas nos mais, é você quem tem que se adaptar a isso.
Quando não usar?
Por mais bonito e moderno que possa parecer, nem tudo pode ou precisa virar um Sistema Distribuído.
Na verdade, a maioria das aplicações pequenas sofre quando alguém tenta distribuir demais.
Se o sistema é simples, o tráfego é baixo e a equipe é pequena, dividir um monolito só vai deixar tudo mais complicado e caro sem necessidade nenhuma.
Você passa a lidar com filas, gateways, observabilidade, retries, idempotência, cada serviço com seu deploy, cada deploy com seus problemas… Ah! Já cansei só de escrever.
Também não faz sentido usar uma arquitetura distribuída quando o domínio da aplicação é pequeno.
Se seu sistema inteiro gira em torno de um único conjunto de regras simples, dividir isso em serviços só atrapalha.
O ganho aparece quando existem partes que realmente evoluem em ritmos diferentes, têm necessidades diferentes e precisam de uma escalabilidade diferente. Se esse não é o caso, um monolito é mais do que o suficiente.
Conclusão
No fim das contas, um Sistema Distribuído não é sobre “usar micro serviços” ou “colocar um RabbitMQ no meio”.
É sobre mudar a forma como você enxerga a própria aplicação.
Quando você distribui, você deixa de imaginar o sistema como uma caixa única e começa a olha pra ele como um conjunto de partes que trabalham juntas, cada uma no seu ritmo, no seu servidor, no seu processo.
E como tudo na computação, é uma questão de trade-off: você tá colocando problemas para resolver outros.
Essa é a estrutura que a maior parte das grades empresas usa. Não só por hype ou modelo de times, mas por uma necessidade de performance, escala e tudo o mais.
E não, a ideia aqui não é te convencer a adotar isso amanhã.
O objetivo é só deixar claro o que essa arquitetura realmente é, o tipo de problema que ela resolve e o tipo de problema novo que ela cria.
Porque no fim do dia, arquitetura não é uma escolha estética.
É uma resposta a uma dor.
E um sistema distribuído só faz sentido quando essa dor aparece.