Antipadrões em Arquitetura de Software
Erros comuns em arquitetura de software distribuída: como identificá-los, seu impacto real e as estratégias para corrigi-los.
O que são os antipadrões
Um antipadrão é uma solução recorrente que parece razoável no início, mas que gera problemas significativos a médio e longo prazo. Diferentemente de um simples erro, os antipadrões são armadilhas comuns nas quais caem times experientes, muitas vezes por pressão de tempo, falta de contexto ou decisões que foram corretas em um momento, mas deixaram de ser.
Reconhecer esses antipadrões a tempo permite que você evite dívida técnica cara e tome decisões arquiteturais mais bem fundamentadas.
Por que surgem os antipadrões
Os antipadrões não aparecem por incompetência. Surgem por razões compreensíveis:
| Causa | Exemplo |
|---|---|
| Pressão de tempo | ”Vamos adicionar essa funcionalidade ao serviço existente, não há tempo para criar um novo” |
| Falta de contexto | ”Todos os serviços podem ler dessa base de dados, é mais fácil” |
| Decisões que envelhecem | ”Quando éramos 3 desenvolvedores isso funcionava bem, agora somos 30” |
| Otimização prematura | ”Vamos separar tudo em microsserviços desde o dia 1” |
| Inércia organizacional | ”Sempre fizemos assim” |
1. Monolito Distribuído (Distributed Monolith)
Problema
Ocorre quando um sistema é dividido em múltiplos serviços que dependem tão fortemente uns dos outros que não podem ser implantados, escalados nem evoluídos de forma independente. Na prática, você tem a complexidade operacional dos microsserviços sem nenhum de seus benefícios.
┌──────────┐ ┌──────────┐ ┌──────────┐
│Servicio A│────►│Servicio B│────►│Servicio C│
│ │◄────│ │◄────│ │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└────────────────┴────────────────┘
Despliegue conjunto obligatorio
Modelos de datos compartidos
Llamadas síncronas en cadena
Os sinais mais claros são:
- Você precisa implantar vários serviços ao mesmo tempo para que uma mudança funcione
- Uma mudança no contrato de um serviço quebra os demais
- Os serviços compartilham bibliotecas internas com lógica de negócio
- Os testes de integração exigem subir o sistema inteiro
Impacto
- Implantações frágeis: Uma mudança em um serviço exige coordenar a implantação de outros, eliminando a agilidade que motivou a separação
- Falhas em cascata: A dependência síncrona entre serviços faz com que a queda de um arraste os demais
- Complexidade sem benefício: O time paga o custo operacional de múltiplos serviços sem ganhar independência real
- Velocidade de desenvolvimento reduzida: Os times não conseguem trabalhar em paralelo porque as mudanças se bloqueiam mutuamente
Como detectá-lo
- Pergunte: posso implantar este serviço sem tocar em nenhum outro? Se a resposta for “não” com frequência, você tem um monolito distribuído
- Verifique se os serviços compartilham esquemas de base de dados ou bibliotecas com lógica de domínio
- Observe se os releases exigem coordenação entre múltiplos times para uma única mudança funcional
- Meça o coupling score: conte quantos serviços são implantados juntos em cada release
Correção
| Estratégia | Descrição |
|---|---|
| Definir contratos claros | Usar APIs versionadas com contratos explícitos. Cada serviço expõe uma interface estável e evolui internamente sem quebrar consumidores |
| Comunicação assíncrona | Substituir chamadas síncronas em cadeia por eventos. Publicar um evento em vez de fazer uma chamada direta |
| Bounded Contexts (DDD) | Aplicar DDD para identificar os limites reais do domínio. Cada serviço deve ser dono do seu contexto |
| Avaliar reconsolidação | Se os serviços estão tão acoplados que não podem ser separados, considere reuni-los novamente. Um monolito bem projetado é melhor que um monolito distribuído |
| Anti-Corruption Layer | Usar ACL entre serviços para traduzir modelos e reduzir o acoplamento direto |
2. Base de Dados Compartilhada (Shared Database)
Problema
Múltiplos serviços leem e escrevem diretamente na mesma base de dados, acessando as mesmas tabelas. Isso cria um acoplamento invisível: os serviços não dependem de APIs explícitas, mas da estrutura interna das tabelas.
┌──────────┐ ┌──────────┐ ┌──────────┐
│Servicio A│ │Servicio B│ │Servicio C│
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────┐
│ Base de Datos Compartida │
│ │
│ ┌─────────┐ ┌─────────┐ ┌────────┐ │
│ │ Tabla X │ │ Tabla Y │ │ Tabla Z│ │
│ └─────────┘ └─────────┘ └────────┘ │
│ │
│ Todos leen y escriben en todas │
└──────────────────────────────────────┘
Os sinais mais claros são:
- Vários serviços fazem queries diretas nas mesmas tabelas
- Uma mudança de esquema exige modificar múltiplos serviços
- Não há um “dono” claro de cada tabela ou conjunto de dados
- As migrações de base de dados são eventos de alto risco
Impacto
- Acoplamento oculto: Os serviços parecem independentes, mas estão atados pela estrutura da base de dados. Um
ALTER TABLEpode quebrar serviços que você nem sabia que usavam aquela tabela - Escalabilidade limitada: Você não consegue escalar a base de dados de forma independente por serviço
- Migrações perigosas: Qualquer mudança de esquema é um risco porque você não sabe quem depende de quê
- Impossibilidade de evoluir: Cada serviço fica atado à tecnologia e à estrutura da base de dados compartilhada
Como detectá-lo
- Verifique se mais de um serviço tem connection strings para a mesma base de dados
- Procure queries que acessam tabelas que “pertencem” a outro domínio funcional
- Pergunte: quem é o dono desta tabela? Se a resposta for “vários serviços”, você tem esse antipadrão
- Execute uma análise de dependências: mapeie quais serviços acessam quais tabelas
Correção
| Estratégia | Descrição |
|---|---|
| Database per Service | Cada serviço tem sua própria base de dados. Somente o serviço dono pode ler e escrever seus dados |
| Expor dados via API | Se o Serviço B precisa de dados do Serviço A, ele os solicita através da API de A |
| Eventos para sincronização | Usar eventos de domínio para que os serviços mantenham cópias locais dos dados de que precisam |
| Outbox Pattern | Publicar eventos de forma confiável quando os dados mudam, para que outros serviços se sincronizem |
| Migração incremental | Não tente separar tudo de uma vez. Identifique as tabelas mais problemáticas e migre serviço por serviço |
3. God Service (Serviço Deus)
Problema
Um único serviço concentra responsabilidades demais e se torna o ponto central por onde passa quase toda a lógica do sistema. Costuma começar como um serviço legítimo que vai acumulando funcionalidades porque “é mais fácil adicionar aqui”.
┌─────────────────────┐
│ God Service │
│ │
│ • Autenticación │
│ • Usuarios │
│ • Pedidos │
│ • Pagos │
│ • Notificaciones │
│ • Reportes │
│ • Inventario │
└──────────┬──────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Servicio X│ │Servicio Y│ │Servicio Z│
│(simple) │ │(simple) │ │(simple) │
└──────────┘ └──────────┘ └──────────┘
Todos dependen del God Service
Os sinais mais claros são:
- Um serviço tem significativamente mais endpoints do que os demais
- A maioria das mudanças funcionais exige modificar esse serviço
- O serviço tem múltiplas responsabilidades não relacionadas entre si
- O time que o mantém é um gargalo constante
Impacto
- Gargalo de desenvolvimento: Todos os times precisam modificar o mesmo serviço
- Ponto único de falha: Se o God Service cai, grande parte do sistema deixa de funcionar
- Escalabilidade desigual: Você não consegue escalar apenas pagamentos se ele estiver empacotado com relatórios
- Complexidade crescente: O serviço se torna difícil de entender, testar e manter
- Implantações arriscadas: Cada implantação toca em muitas funcionalidades
Como detectá-lo
- Meça as linhas de código ou endpoints por serviço. Se um tiver 10x mais que a média, é suspeito
- Revise os commits: se um serviço concentra a maioria das mudanças de múltiplos times, provavelmente é um God Service
- Pergunte: consigo descrever a responsabilidade deste serviço em uma frase? Se você precisa de uma lista longa, ele tem responsabilidades demais
- Analise as dependências: se a maioria dos serviços depende de um só, é um God Service
Correção
| Estratégia | Descrição |
|---|---|
| Identificar bounded contexts (DDD) | Mapear os domínios reais dentro do God Service. Cada domínio é candidato a ser um serviço independente |
| Strangler Fig Pattern | Extrair funcionalidades uma a uma, criando novos serviços que interceptam as chamadas ao God Service |
| Princípio da responsabilidade única | Cada serviço deve ter uma razão clara para existir |
| Priorizar por impacto | Comece extraindo a funcionalidade que muda com mais frequência ou que tem requisitos de escalabilidade diferentes |
| API Gateway / BFF | Usar um gateway para rotear requests a serviços especializados em vez de ao God Service |
4. Serviços Tagarelas (Chatty Services)
Problema
Os serviços se comunicam entre si com chamadas demais de granularidade fina para completar uma única operação. Em vez de uma solicitação que obtenha toda a informação necessária, um serviço faz múltiplas chamadas pequenas.
Servicio A necesita mostrar el perfil de un usuario:
Servicio A → Servicio Usuarios: GET /users/123
Servicio A → Servicio Usuarios: GET /users/123/preferences
Servicio A → Servicio Pedidos: GET /orders?user=123
Servicio A → Servicio Pedidos: GET /orders/456/details
Servicio A → Servicio Pedidos: GET /orders/789/details
Servicio A → Servicio Pagos: GET /payments?user=123
Servicio A → Servicio Reviews: GET /reviews?user=123
Total: 7 llamadas de red para una sola pantalla
Os sinais mais claros são:
- Uma operação de negócio gera mais de 3-4 chamadas entre serviços
- A latência é alta ainda que cada serviço individual responda rápido
- Os logs mostram rajadas de chamadas correlacionadas para um único request
Impacto
- Latência acumulada: 7 chamadas de 50ms cada uma somam 350ms no melhor caso (sequencial)
- Fragilidade: Com 7 chamadas e 99% de disponibilidade por serviço, a disponibilidade efetiva cai para ~93%
- Carga de rede excessiva: O tráfego interno pode superar o tráfego externo
- Dificuldade para depurar: Rastrear um erro através de múltiplas chamadas exige rastreabilidade distribuída
Como detectá-lo
- Conte as chamadas entre serviços que um único request do usuário gera
- Meça a latência end-to-end vs. a latência individual de cada serviço
- Verifique se os serviços expõem endpoints muito granulares
Correção
| Estratégia | Descrição |
|---|---|
| BFF (Backend for Frontend) | Criar um serviço intermediário que agregue dados de múltiplos serviços em uma única resposta |
| Endpoints agregados | Projetar endpoints que retornem toda a informação necessária para um caso de uso |
| Cache local | Manter cópias locais de dados que mudam pouco |
| Chamadas em paralelo | Se as chamadas forem independentes, executá-las em paralelo |
| Eventos para dados derivados | Usar eventos para que cada serviço mantenha uma visão materializada dos dados de que precisa |
5. Modelo Anêmico (Anemic Domain Model)
Problema
As entidades do domínio são contêineres de dados sem comportamento — só têm getters e setters. Toda a lógica de negócio vive em serviços externos que manipulam essas entidades passivas.
// ❌ Modelo anémico
class Pedido {
id: string;
estado: string;
líneas: LíneaPedido[];
total: number;
// Solo getters y setters, sin lógica
}
class PedidoService {
confirmar(pedido: Pedido) {
if (pedido.estado !== 'borrador') throw new Error('...');
if (pedido.líneas.length === 0) throw new Error('...');
pedido.estado = 'confirmado';
pedido.total = this.calcularTotal(pedido.líneas);
// Toda la lógica está aquí, no en Pedido
}
}
// ✅ Modelo rico
class Pedido {
confirmar() {
if (this.estado !== 'borrador')
throw new Error('Solo se puede confirmar un pedido en borrador');
if (this.líneas.length === 0)
throw new Error('El pedido debe tener al menos una línea');
this.estado = 'confirmado';
this.total = this.calcularTotal();
this.registrarEvento(new PedidoConfirmado(this.id));
}
}
Impacto
- Lógica dispersa: As regras de negócio são duplicadas em múltiplos serviços
- Difícil de testar: Os testes precisam configurar serviços externos para validar regras simples
- Violação de encapsulamento: Qualquer um pode modificar o estado da entidade sem passar pelas regras
- Difícil de descobrir: Não é óbvio quais operações são válidas sobre uma entidade
Correção
| Estratégia | Descrição |
|---|---|
| Mover a lógica para as entidades | As regras de negócio que operam sobre os dados de uma entidade devem viver na entidade |
| Aplicar DDD | Usar agregados com invariantes que protejam a consistência |
| Value Objects | Encapsular conceitos como Dinheiro, Email, Endereço com validação própria |
| Domain Events | As entidades publicam eventos quando mudam de estado |
6. Integração por Base de Dados (Database Integration)
Problema
Os serviços se integram lendo e escrevendo nas tabelas de outros serviços em vez de se comunicarem por APIs ou eventos. É uma variante mais sutil da base de dados compartilhada.
Servicio A necesita saber si un usuario es premium:
❌ SELECT tipo FROM usuarios WHERE id = 123
(accede directamente a la tabla del Servicio B)
✅ GET /api/users/123/subscription
(consulta al Servicio B por su API)
Impacto
- Acoplamento invisível: Não há contrato explícito, apenas conhecimento implícito da estrutura de tabelas
- Impossível versionar: Você não consegue evoluir o esquema sem quebrar os consumidores ocultos
- Sem controle de acesso: Qualquer serviço pode ler ou escrever qualquer dado
Correção
| Estratégia | Descrição |
|---|---|
| APIs explícitas | Cada serviço expõe uma API para acessar seus dados |
| Eventos de domínio | Publicar mudanças como eventos para que outros serviços se inscrevam |
| ACL Pattern | Criar uma camada de tradução entre o modelo interno e os consumidores |
Resumo comparativo
| Antipadrão | Sinal principal | Maior risco | Primeira ação |
|---|---|---|---|
| Monolito Distribuído | Você não consegue implantar um serviço sozinho | Complexidade sem benefício | Definir contratos e bounded contexts |
| Base de Dados Compartilhada | Vários serviços acessam as mesmas tabelas | Migrações perigosas | Atribuir ownership das tabelas |
| God Service | Um serviço concentra toda a lógica | Ponto único de falha | Identificar domínios para extrair |
| Serviços Tagarelas | Muitas chamadas para uma operação | Latência e fragilidade | Criar endpoints agregados ou BFF |
| Modelo Anêmico | Entidades sem comportamento | Lógica dispersa e duplicada | Mover a lógica para as entidades (DDD) |
| Integração por BD | Queries diretas em tabelas de outros serviços | Acoplamento invisível | Expor APIs explícitas |
Como prevenir antipadrões
Os antipadrões raramente aparecem de uma vez. Acumulam-se gradualmente. Algumas práticas que ajudam a preveni-los:
- Revisões de arquitetura periódicas: Avalie regularmente se os serviços mantêm sua independência
- Fitness functions: Defina métricas automatizadas que detectem acoplamento (número de dependências, tamanho de serviços, frequência de implantações conjuntas)
- ADRs (Architecture Decision Records): Documente as decisões arquiteturais e suas razões
- Ownership claro: Cada serviço, tabela e API deve ter um time responsável
- Princípio da navalha: Se você não consegue justificar por que um serviço é independente, provavelmente ele não deveria ser
- Monitoramento de acoplamento: Meça quantos serviços são implantados juntos, quantas chamadas síncronas há entre serviços e quantas tabelas são compartilhadas
Relação com outros padrões
Os antipadrões são prevenidos e corrigidos aplicando os padrões corretos:
Anti-patrón Patrón que lo corrige
─────────────────────────────────────────────────────
Monolito Distribuido → DDD (Bounded Contexts)
→ Event-Driven Architecture
→ ACL Pattern
Base de Datos Compartida → Database per Service
→ Outbox Pattern (sincronización)
→ Event-Driven Architecture
God Service → Microservices Pattern
→ DDD (descomposición por dominio)
→ API Gateway / BFF
Servicios Parlanchines → BFF Pattern
→ API Gateway Pattern
→ Event-Driven Architecture
Modelo Anémico → DDD (diseño táctico)
→ Aggregates + Domain Events
Integración por BD → ACL Pattern
→ Outbox Pattern
→ Shared Kernel (controlado)
Próximos passos
Agora que você conhece os antipadrões mais comuns, aprofunde-se nos padrões que os previnem: DDD para definir bounded contexts claros, Microservices Pattern para decompor corretamente, Event-Driven Architecture para desacoplar serviços e BFF Pattern para resolver o problema dos serviços tagarelas.