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érioMonolitoMicrosserviços
Complexidade inicialBaixaAlta
EscalabilidadeVertical (mais recursos)Horizontal (mais instâncias)
ImplantaçãoTudo juntoIndependente por serviço
Consistência de dadosACID nativoConsistência eventual
Tamanho de equipe ideal2-8 pessoasMúltiplas equipes (8+)
Time to market inicialRápidoLento
Custo operacionalBaixoAlto
Autonomia das equipesLimitadaAlta
Isolamento de falhasBaixoAlto
Flexibilidade tecnológicaUma única stackMú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:

  1. Começar com um sistema simples
  2. Modularizá-lo bem
  3. Identificar domínios e limites reais
  4. 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:

  1. Identifique um módulo com limites claros.
  2. Crie um novo serviço que replique essa funcionalidade.
  3. Redirecione o tráfego para o novo serviço.
  4. 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.