Monolito vs Microsserviços
Comparação entre arquitetura monolítica e microsserviços: vantagens, desvantagens, quando usar cada uma e considerações sobre migração.
Duas abordagens arquiteturais distintas
Quando você projeta um sistema, uma das primeiras decisões é como organizar o código e os processos. Os dois extremos do espectro são a arquitetura monolítica e os microsserviços. Nenhum é inerentemente melhor; cada um resolve problemas diferentes em contextos diferentes.
O que é um monolito?
Um monolito é uma aplicação que agrupa múltiplas capacidades do sistema dentro de uma única unidade implantável.
Isso não significa necessariamente que ele esteja mal projetado. Um monolito pode ser bem estruturado internamente, com módulos, camadas e limites claros. O importante é que, do ponto de vista operacional, ele é implantado como uma única aplicação.
Estrutura típica
minha-aplicacao/
├── src/
│ ├── controllers/ # Endpoints HTTP
│ ├── services/ # Lógica de negócio
│ ├── models/ # Modelos de dados
│ ├── repositories/ # Acesso ao banco de dados
│ └── utils/ # Utilidades compartilhadas
├── database/
│ └── migrations/
└── package.json
O que são os microsserviços?
Os microsserviços separam o sistema em serviços menores, cada um responsável por um domínio ou capacidade específica.
Cada serviço costuma ter:
- Sua própria lógica de negócio
- Sua implantação independente
- Sua persistência ou acesso a dados próprio
- Seus contratos de entrada e saída
A ideia não é “criar serviços pequenos por criar”, mas sim estabelecer limites que permitam evoluir partes do sistema com menor acoplamento.
Estrutura típica
sistema/
├── servico-usuarios/
│ ├── src/
│ ├── Dockerfile
│ └── package.json
├── servico-pedidos/
│ ├── src/
│ ├── Dockerfile
│ └── package.json
├── servico-pagamentos/
│ ├── src/
│ ├── Dockerfile
│ └── package.json
└── api-gateway/
├── src/
└── package.json
Vantagens do monolito
Simplicidade inicial
Um único projeto costuma ser mais fácil de iniciar, implantar e depurar. Você não precisa resolver desde o primeiro dia temas como mensageria, rastreabilidade distribuída ou implantações coordenadas.
Menor complexidade operacional
Você não precisa de orquestração de contêineres, service discovery, monitoramento distribuído nem gestão de consistência eventual desde o início.
Mais fácil para equipes pequenas
Quando a equipe é reduzida (2-8 pessoas), muitas vezes é mais eficiente trabalhar sobre uma única base de código bem organizada.
Transações ACID
As operações de banco de dados são simples porque tudo compartilha a mesma conexão. Você não precisa de padrões como Saga ou Outbox para manter a consistência.
Mais simples de testar end-to-end
A integração entre módulos ocorre em memória ou dentro de um mesmo processo, o que reduz o atrito ao testar fluxos completos.
Menor latência interna
As chamadas entre módulos são chamadas de função em memória, não chamadas de rede. Isso elimina a latência inerente à comunicação entre serviços.
Desvantagens do monolito
Acoplamento crescente
Se não houver disciplina interna, o sistema pode se tornar difícil de manter. Com o tempo, os módulos tendem a se entrelaçar e as dependências ficam difíceis de rastrear.
Implantação única
Alterar uma parte obriga a implantar toda a aplicação. Uma mudança em um módulo exige reimplantar todo o sistema, o que aumenta o risco.
Escalabilidade menos granular
Você não pode escalar apenas a parte que recebe mais carga. É preciso escalar toda a aplicação, mesmo que só um módulo precise de mais recursos.
Menor autonomia organizacional
Com várias equipes, coordenar mudanças pode se tornar custoso. Toda a equipe precisa usar a mesma linguagem e o mesmo framework.
Vantagens dos microsserviços
Separação por domínios
Permite isolar capacidades do negócio e suas regras. Cada serviço encapsula um bounded context com seu próprio modelo de dados.
Implantações independentes
Cada serviço pode evoluir no seu próprio ritmo sem afetar os demais. Uma equipe pode implantar seu serviço sem coordenar com outras equipes.
Escalabilidade mais granular
Você pode escalar apenas o que realmente precisa. Se o serviço de busca recebe 10x mais tráfego que o de faturamento, você escala apenas a busca.
Equipes mais autônomas
Cada equipe pode se responsabilizar por um bounded context ou domínio completo, tomando decisões técnicas de forma independente.
Isolamento de falhas
Se um serviço cai, os demais podem continuar funcionando (com o design adequado). Isso melhora a resiliência geral do sistema.
Flexibilidade tecnológica
Em certos casos, permite escolher tecnologias distintas por serviço. Um serviço pode usar Node.js enquanto outro usa Go, conforme as necessidades.
Desvantagens dos microsserviços
Complexidade operacional
Surgem problemas que em um monolito não existem:
- Descoberta de serviços
- Observabilidade distribuída
- Retentativas e idempotência
- Falhas de rede
- Versionamento de contratos
- Orquestração de contêineres
Latência de rede
Cada chamada entre serviços passa pela rede, o que adiciona latência. Um fluxo que em um monolito é uma chamada de função, em microsserviços pode envolver múltiplos saltos de rede.
Consistência mais complexa
As transações distribuídas não são triviais. Você precisa aceitar consistência eventual em muitos casos e usar padrões como Saga para coordenar operações entre serviços.
Depuração difícil
Rastrear um erro através de múltiplos serviços exige ferramentas de tracing distribuído como Jaeger ou Zipkin. Sem elas, diagnosticar problemas é extremamente difícil.
Mais esforço de governança
É preciso definir padrões técnicos, ownership e regras de integração. Sem governança, cada equipe pode tomar decisões incompatíveis.
Duplicação de código
Lógica comum pode acabar duplicada entre serviços se não for gerenciada adequadamente com shared kernels ou bibliotecas compartilhadas.
Mais infraestrutura
Você precisa de pipelines de CI/CD, implantação automatizada, monitoramento e operação mais maduros desde o início.
Comparação direta
| Critério | Monolito | Microsserviços |
|---|---|---|
| Complexidade inicial | Baixa | Alta |
| Escalabilidade | Vertical (mais recursos) | Horizontal (mais instâncias) |
| Implantação | Tudo junto | Independente por serviço |
| Consistência de dados | ACID nativo | Consistência eventual |
| Tamanho de equipe ideal | 2-8 pessoas | Múltiplas equipes (8+) |
| Time to market inicial | Rápido | Lento |
| Custo operacional | Baixo | Alto |
| Autonomia das equipes | Limitada | Alta |
| Isolamento de falhas | Baixo | Alto |
| Flexibilidade tecnológica | Uma única stack | Múltiplas stacks possíveis |
Qual é melhor?
A pergunta correta não é “o que é melhor em geral”, mas sim:
- Qual resolve melhor as minhas necessidades atuais?
- Qual eu consigo operar com a equipe que tenho?
- Que nível de complexidade de negócio eu gerencio?
- Preciso de implantações realmente independentes?
- Tenho a maturidade técnica para sustentá-lo?
Escolha o monolito quando
- Você está começando um projeto novo e o domínio ainda não está completamente definido.
- Sua equipe é pequena (menos de 8 pessoas).
- Você precisa chegar ao mercado rapidamente (MVP).
- O sistema não exige escalabilidade diferenciada por módulo.
- Você não tem experiência operando infraestrutura distribuída.
Escolha microsserviços quando
- O sistema cresceu e as equipes se atrapalham mutuamente ao fazer mudanças.
- Você precisa escalar módulos específicos de forma independente.
- Você tem equipes autônomas que podem ser donas de serviços completos.
- O domínio de negócio tem limites claros entre contextos (bounded contexts).
- Você conta com a infraestrutura e a experiência para operar sistemas distribuídos.
O caminho intermediário: monolito modular
Em muitos casos, um monolito modular é uma ótima solução inicial. E, em alguns contextos, pode continuar sendo por muito tempo.
É um monolito com limites internos bem definidos entre módulos:
minha-aplicacao/
├── modules/
│ ├── usuarios/
│ │ ├── controllers/
│ │ ├── services/
│ │ └── models/
│ ├── pedidos/
│ │ ├── controllers/
│ │ ├── services/
│ │ └── models/
│ └── pagamentos/
│ ├── controllers/
│ ├── services/
│ └── models/
└── shared/
└── utils/
Cada módulo tem sua própria estrutura interna e se comunica com os outros módulos por meio de interfaces bem definidas. Isso oferece muitos dos benefícios dos microsserviços (baixo acoplamento, alta coesão) sem a complexidade operacional.
Evolução realista
Um erro comum é tentar começar com microsserviços cedo demais. Isso costuma gerar mais complexidade técnica do que valor real.
Uma evolução mais saudável costuma ser:
- Começar com um sistema simples
- Modularizá-lo bem
- Identificar domínios e limites reais
- Separar apenas quando houver pressão real para fazê-lo
Ou seja: primeiro entender o domínio, depois decidir a forma arquitetural.
Padrão Strangler Fig
Se você decidir migrar de monolito para microsserviços, não reescreva tudo de uma vez. Extraia funcionalidades gradualmente:
- Identifique um módulo com limites claros.
- Crie um novo serviço que replique essa funcionalidade.
- Redirecione o tráfego para o novo serviço.
- Remova o código do monolito quando o serviço estiver estável.
Erros comuns na migração
- Microsserviços pequenos demais: Se um serviço não consegue funcionar sem chamar constantemente outros, provavelmente ele é pequeno demais.
- Banco de dados compartilhado: Se dois serviços compartilham o mesmo banco de dados, eles não são realmente independentes.
- Migrar sem automação: Sem CI/CD, monitoramento e orquestração, os microsserviços se tornam ingerenciáveis.
Resumo
Monolito e microsserviços não são “bom vs ruim”. São estratégias distintas para organizar um sistema. O monolito costuma oferecer simplicidade inicial; os microsserviços oferecem separação e autonomia ao custo de maior complexidade operacional.
A melhor arquitetura é aquela que resolve o seu problema atual sem criar problemas futuros desnecessários. Comece simples, meça e evolua quando os dados justificarem.