Anti-patterns de microsserviços: o que não fazer

Microsserviços prometem escalabilidade, autonomia de equipes e resiliência. No entanto, quando implementados sem disciplina, geram sistemas mais frágeis e complexos que um monólito bem estruturado. Este artigo explora os anti-patterns mais comuns e como evitá-los, usando exemplos práticos de código.

1. O Monólito Distribuído: A Pior Forma de Microsserviço

O monólito distribuído é a pior combinação possível: acoplamento de monólito com latência de rede. Ele surge quando serviços se comunicam de forma síncrona em cadeia e compartilham bancos de dados.

// Exemplo de acoplamento síncrono em cadeia (anti-pattern)
Serviço A (Pedidos) -> chamada HTTP -> Serviço B (Pagamentos)
Serviço B -> chamada HTTP -> Serviço C (Estoque)
Serviço C -> chamada HTTP -> Serviço D (Faturamento)
// Se D falha, toda a cadeia quebra. Latência total = soma de todas as chamadas.

Solução: Use comunicação assíncrona com eventos e filas. Cada serviço deve ter seu próprio banco de dados, sem compartilhamento de esquemas.

// Exemplo correto com eventos assíncronos
Serviço A publica evento "PedidoCriado" no Kafka
Serviço B consome evento e processa pagamento
Serviço C consome evento e reserva estoque
// Cada serviço é independente e falhas não se propagam

2. Granularidade Errada: Serviços Muito Pequenos ou Muito Grandes

Microsserviços anêmicos realizam apenas operações CRUD em uma única entidade, enquanto "god services" concentram múltiplos domínios.

// Anti-pattern: Serviço anêmico
Serviço UsuarioCRUD
  - GET /usuarios/{id}
  - POST /usuarios
  - PUT /usuarios/{id}
  - DELETE /usuarios/{id}
// Sem lógica de negócio relevante, apenas operações de banco

// Anti-pattern: Serviço god object
Serviço SistemaCompleto
  - GET /pedidos
  - POST /pagamentos
  - PUT /estoque
  - GET /relatorios
  - POST /notificacoes
// Mistura funcionalidades de diferentes contextos delimitados

Solução: Mapeie cada serviço a um Bounded Context do Domain-Driven Design. Um serviço deve encapsular um agregado completo com suas regras de negócio.

// Serviço bem projetado: Gerencia todo o ciclo de vida de pedidos
Serviço GerenciamentoPedidos
  - POST /pedidos (cria pedido com itens, valida regras de negócio)
  - GET /pedidos/{id}/status
  - POST /pedidos/{id}/cancelar
  - Eventos: PedidoCriado, PedidoCancelado, PedidoPago

3. Comunicação Síncrona e Cascata de Falhas

Encadeamento profundo de chamadas síncronas (request waterfall) sem resiliência é receita para desastres.

// Anti-pattern: Chamada síncrona sem proteção
function processarPedido(pedidoId) {
  const usuario = await http.get(`/usuarios/${pedidoId.usuarioId}`)
  const pagamento = await http.get(`/pagamentos/${pedidoId.pagamentoId}`)
  const estoque = await http.get(`/estoque/${pedidoId.produtoId}`)
  // Se qualquer chamada falha, toda a operação falha
  // Sem timeout, sem circuit breaker, sem fallback
}

Solução: Implemente padrões de resiliência: Circuit Breaker, Retry com backoff, Timeouts e Fallbacks.

// Exemplo com padrões de resiliência
async function processarPedidoSeguro(pedidoId) {
  try {
    const usuario = await chamarComTimeout('/usuarios/...', 2000)
    const pagamento = await chamarComCircuitBreaker('/pagamentos/...', 3000)
    // Fallback se pagamento falhar
    if (!pagamento) {
      await enviarParaFilaRetentativa(pedidoId)
      return { status: 'pendente', mensagem: 'Pagamento será processado' }
    }
  } catch (erro) {
    return { status: 'erro', mensagem: 'Serviço temporariamente indisponível' }
  }
}

4. Gestão de Dados e Transações Distribuídas

Usar transações distribuídas (2PC/XA) entre microsserviços quebra a autonomia e introduz latência e deadlocks.

// Anti-pattern: Transação distribuída com 2PC
try {
  await bancoA.iniciarTransacao()  // Serviço Pedidos
  await bancoB.iniciarTransacao()  // Serviço Pagamentos
  await bancoC.iniciarTransacao()  // Serviço Estoque
  // Se qualquer um falha, todos precisam fazer rollback
  // Latência alta, bloqueios prolongados
} catch (erro) {
  await bancoA.rollback()
  await bancoB.rollback()
  await bancoC.rollback()
}

Solução: Use padrões de consistência eventual com Sagas (coreografadas ou orquestradas) e garanta idempotência.

// Exemplo de Saga coreografada com eventos
1. Serviço Pedidos cria pedido (status: pendente)
2. Publica evento "PedidoCriado"
3. Serviço Pagamentos consome e processa pagamento
4. Publica evento "PagamentoConfirmado" ou "PagamentoRecusado"
5. Serviço Pedidos consome evento e atualiza status
6. Se pagamento recusado, publica "PedidoCancelado"

// Idempotência: cada mensagem tem ID único
async function processarPagamento(evento) {
  if (await jaProcessado(evento.id)) return // idempotente
  await processar(evento)
  await marcarComoProcessado(evento.id)
}

5. Acoplamento na Camada de Comunicação e Infraestrutura

Compartilhar bibliotecas de modelo de domínio (JARs compartilhados) ou depender de middleware específico cria acoplamento forte.

// Anti-pattern: Biblioteca compartilhada de modelos
// Todos os serviços importam "modelo-compartilhado.jar"
// Qualquer mudança no modelo quebra todos os serviços
// Versões precisam ser sincronizadas

// Anti-pattern: Dependência de versão específica de middleware
// Serviço A só funciona com RabbitMQ 3.8.x
// Serviço B só funciona com RabbitMQ 3.9.x
// Atualização do middleware afeta todos os serviços

Solução: Cada serviço define seus próprios modelos de domínio e contratos de API. Use contratos formais (OpenAPI, gRPC) com versionamento semântico.

// Contrato formal com OpenAPI (versão 2.0)
openapi: "3.0.0"
info:
  title: Serviço de Pedidos
  version: "2.0.0"
paths:
  /pedidos:
    post:
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                clienteId:
                  type: string
                itens:
                  type: array
                  items:
                    $ref: '#/components/schemas/ItemPedido'

6. Ignorar Observabilidade e Governança

Sem logging centralizado, métricas e distributed tracing, diagnosticar problemas em 20 microsserviços é impossível.

// Anti-pattern: Logging local sem correlação
// Serviço A: console.log("Pedido criado: " + id)
// Serviço B: console.log("Pagamento processado")
// Sem ID de correlação, não é possível rastrear uma requisição completa

// Anti-pattern: API sem versionamento
// GET /api/pedidos (versão implícita)
// Mudança quebra consumidores sem aviso

Solução: Implemente distributed tracing com IDs de correlação, logging estruturado, métricas e versionamento explícito de APIs.

// Logging estruturado com correlation ID
{
  "correlationId": "abc-123-def",
  "servico": "pedidos",
  "operacao": "criarPedido",
  "nivel": "info",
  "mensagem": "Pedido criado com sucesso",
  "dados": { "pedidoId": "456", "clienteId": "789" },
  "timestamp": "2024-01-15T10:30:00Z"
}

// API versionada explicitamente
GET /api/v2/pedidos
GET /api/v1/pedidos

7. Complexidade Acidental e Over-engineering

Introduzir microsserviços em sistemas simples ou usar tecnologias complexas sem necessidade real aumenta custos operacionais.

// Anti-pattern: Microsserviços para aplicação de blog simples
// Em vez de um monólito simples, criaram:
// - Serviço de Autenticação (Node.js)
// - Serviço de Posts (Go)
// - Serviço de Comentários (Python)
// - Service Mesh (Istio)
// - Event Sourcing (Kafka)
// - 3 bancos de dados diferentes
// Custo operacional 10x maior que o monólito equivalente

// Anti-pattern: Over-engineering com tecnologias desnecessárias
// Para uma API simples de CRUD, usaram:
// - CQRS com Event Sourcing
// - Saga orquestrada com 5 microsserviços
// - Service Mesh com circuit breakers
// - 2 sistemas de mensageria diferentes

Solução: Comece com um monólito modular. Extraia serviços apenas quando houver necessidade real de escalabilidade independente ou autonomia de equipes. Avalie o custo real de cada tecnologia.

// Abordagem pragmática: Monólito modular primeiro
módulo-pedidos/
  - PedidoController.java
  - PedidoService.java
  - PedidoRepository.java
módulo-pagamentos/
  - PagamentoController.java
  - PagamentoService.java
  - PagamentoRepository.java
// Módulos bem separados, mas no mesmo deploy
// Extrair para microsserviço apenas quando necessário

Conclusão

Microsserviços são uma ferramenta poderosa, mas exigem disciplina. Evitar os anti-patterns descritos — monólito distribuído, granularidade inadequada, comunicação síncrona sem resiliência, transações distribuídas, acoplamento de infraestrutura, falta de observabilidade e over-engineering — é essencial para colher os benefícios reais da arquitetura.

Lembre-se: um monólito bem projetado é superior a um sistema de microsserviços mal projetado. A complexidade deve ser justificada pelo valor de negócio.

Referências