Saltar para o conteúdo
Microsserviços vs Monólito: A decisão que vai definir seus próximos anos
Arquitetura de Software

Microsserviços vs Monólito: A decisão que vai definir seus próximos anos

30 de setembro de 2024·Paulo de Paula

Toda semana alguém começa um novo projeto e pergunta: “devo usar microsserviços?” A resposta honesta é: provavelmente não, pelo menos não ainda.

O que os posts de hype não te contam

Microsserviços são a arquitetura de empresas como Netflix, Uber e Amazon depois de anos evoluindo um monólito. Eles resolvem problemas que surgem na escala desses sistemas. Se você não tem esses problemas, não precisa da solução.

Custos reais de microsserviços:

  • Latência de rede entre serviços (o que era uma chamada de função vira uma requisição HTTP)
  • Transações distribuídas são extremamente difíceis
  • Observabilidade: você precisa de distributed tracing, log correlation, health checks por serviço
  • Deploy: orquestração com Kubernetes ou ECS, service discovery, circuit breakers
  • Equipe: cada serviço precisa de ownership claro

Quando o monólito é a resposta certa

Para a maioria dos projetos, o monólito é mais simples, mais rápido de desenvolver e mais fácil de operar:

Monólito bem estruturado
├── src/
│   ├── modules/
│   │   ├── usuarios/
│   │   │   ├── usuarios.controller.ts
│   │   │   ├── usuarios.service.ts
│   │   │   └── usuarios.repository.ts
│   │   ├── pedidos/
│   │   │   ├── pedidos.controller.ts
│   │   │   ├── pedidos.service.ts
│   │   │   └── pedidos.repository.ts
│   │   └── pagamentos/
│   └── shared/

Um monólito modular com fronteiras bem definidas entre domínios é muito mais fácil de extrair em serviços quando necessário.

O critério real: fronteiras de equipe

Conway’s Law: “Qualquer organização que projeta um sistema irá produzir um design cuja estrutura é uma cópia da estrutura de comunicação da organização.”

Se você tem 3 desenvolvedores, um monólito. Se você tem 10 times independentes que precisam deployar de forma autônoma, microsserviços fazem sentido.

Microsserviços resolvem:

  • Autonomia de times — cada time faz deploy sem coordenar com outros
  • Escalabilidade granular — só o serviço de relatórios precisa de mais instâncias
  • Isolamento de falhas — um serviço caindo não derruba o sistema todo
  • Tecnologias heterogêneas — cada serviço pode usar a linguagem certa para o problema

A abordagem do “Monólito Modular” (o meio-termo inteligente)

// Fronteiras claras entre módulos sem deploy separado
// modulos/pedidos/pedidos.service.ts

import { UsuariosService } from '../usuarios/usuarios.service'  // interface, não HTTP
import { PagamentosService } from '../pagamentos/pagamentos.service'

@Injectable()
export class PedidosService {
  constructor(
    private usuarios: UsuariosService,
    private pagamentos: PagamentosService,
    private pedidosRepo: PedidosRepository,
  ) {}

  async criarPedido(dto: CriarPedidoDto) {
    const usuario = await this.usuarios.buscarPorId(dto.usuarioId)
    const pedido = await this.pedidosRepo.criar(dto)
    await this.pagamentos.iniciarCobranca(pedido)
    return pedido
  }
}

Quando você quiser extrair pagamentos como microsserviço, a interface já está definida — você só troca a chamada direta por uma chamada HTTP ou mensagem numa fila.

Quando extrair um serviço faz sentido

Sinais de que um módulo está pronto para virar microsserviço:

  1. Time dedicado — existe um time que cuida só daquele domínio
  2. Escalabilidade diferente — o módulo tem características de carga muito diferentes do resto
  3. Tecnologia diferente — o problema pede um runtime ou banco específico
  4. Isolamento de deploy — falhas naquele módulo não podem afetar o sistema todo
  5. Fronteira de dados limpa — o módulo tem suas próprias tabelas, sem joins com outros módulos

Se nenhum desses critérios se aplica, não extraia.

Comunicação entre serviços: síncrona vs assíncrona

Quando você tiver microsserviços, prefira comunicação assíncrona para operações que não precisam de resposta imediata:

// Síncrono (HTTP) — use para leituras e operações que precisam de resultado
const usuario = await http.get(`/usuarios/${id}`)

// Assíncrono (fila) — use para operações que podem ser processadas depois
await sqs.send('pedido-criado', { pedidoId, usuarioId })

// Consumidor separado
queue.on('pedido-criado', async ({ pedidoId, usuarioId }) => {
  await enviarEmailConfirmacao(usuarioId, pedidoId)
  await atualizarInventario(pedidoId)
})

Checklist antes de adotar microsserviços

  • Você tem times independentes que precisam deployar de forma autônoma?
  • Você tem expertise em Kubernetes ou ECS para operar os serviços?
  • Você tem observabilidade (traces, métricas, logs centralizados) configurada?
  • Você entende como fazer transações distribuídas (Saga, eventual consistency)?
  • O custo operacional adicional é justificável pelo problema que resolve?

Se respondeu “não” para a maioria: comece com o monólito. Você pode sempre extrair depois.