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:

CausaExemplo
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égiaDescrição
Definir contratos clarosUsar APIs versionadas com contratos explícitos. Cada serviço expõe uma interface estável e evolui internamente sem quebrar consumidores
Comunicação assíncronaSubstituir 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çãoSe 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 LayerUsar 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 TABLE pode 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égiaDescrição
Database per ServiceCada serviço tem sua própria base de dados. Somente o serviço dono pode ler e escrever seus dados
Expor dados via APISe o Serviço B precisa de dados do Serviço A, ele os solicita através da API de A
Eventos para sincronizaçãoUsar eventos de domínio para que os serviços mantenham cópias locais dos dados de que precisam
Outbox PatternPublicar eventos de forma confiável quando os dados mudam, para que outros serviços se sincronizem
Migração incrementalNã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égiaDescriçã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 PatternExtrair funcionalidades uma a uma, criando novos serviços que interceptam as chamadas ao God Service
Princípio da responsabilidade únicaCada serviço deve ter uma razão clara para existir
Priorizar por impactoComece extraindo a funcionalidade que muda com mais frequência ou que tem requisitos de escalabilidade diferentes
API Gateway / BFFUsar 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égiaDescrição
BFF (Backend for Frontend)Criar um serviço intermediário que agregue dados de múltiplos serviços em uma única resposta
Endpoints agregadosProjetar endpoints que retornem toda a informação necessária para um caso de uso
Cache localManter cópias locais de dados que mudam pouco
Chamadas em paraleloSe as chamadas forem independentes, executá-las em paralelo
Eventos para dados derivadosUsar 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égiaDescrição
Mover a lógica para as entidadesAs regras de negócio que operam sobre os dados de uma entidade devem viver na entidade
Aplicar DDDUsar agregados com invariantes que protejam a consistência
Value ObjectsEncapsular conceitos como Dinheiro, Email, Endereço com validação própria
Domain EventsAs 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égiaDescrição
APIs explícitasCada serviço expõe uma API para acessar seus dados
Eventos de domínioPublicar mudanças como eventos para que outros serviços se inscrevam
ACL PatternCriar uma camada de tradução entre o modelo interno e os consumidores

Resumo comparativo

AntipadrãoSinal principalMaior riscoPrimeira ação
Monolito DistribuídoVocê não consegue implantar um serviço sozinhoComplexidade sem benefícioDefinir contratos e bounded contexts
Base de Dados CompartilhadaVários serviços acessam as mesmas tabelasMigrações perigosasAtribuir ownership das tabelas
God ServiceUm serviço concentra toda a lógicaPonto único de falhaIdentificar domínios para extrair
Serviços TagarelasMuitas chamadas para uma operaçãoLatência e fragilidadeCriar endpoints agregados ou BFF
Modelo AnêmicoEntidades sem comportamentoLógica dispersa e duplicadaMover a lógica para as entidades (DDD)
Integração por BDQueries diretas em tabelas de outros serviçosAcoplamento invisívelExpor 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.