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
- Martin Fowler - Microservices Guide — Guia completo sobre conceitos, padrões e anti-patterns de microsserviços pelo autor original do termo.
- Microsoft - Microservices Anti-patterns — Documentação oficial da Microsoft sobre anti-patterns em arquiteturas distribuídas, incluindo microsserviços.
- Chris Richardson - Microservices Patterns — Catálogo de padrões de microsserviços, incluindo Sagas, Circuit Breaker e Database per Service.
- Uber Engineering - Distributed Tracing — Artigo técnico sobre implementação de observabilidade e tracing em sistemas de microsserviços.
- Netflix TechBlog - Circuit Breaker Pattern — Explicação detalhada do padrão Circuit Breaker usado em produção pela Netflix.
- Amazon AWS - Microservices Anti-patterns — Blog da AWS com exemplos práticos de anti-patterns e soluções em ambientes cloud.