Módulo 10: Paradigmas de Programação & Padrões de Projeto • Bloco 5: Arquitetura e Enterprise Patterns

🎯 O Negócio no Centro, a Tecnologia na Borda: Frameworks, bancos de dados e provedores de nuvem mudam a cada poucos anos; as regras que fazem uma empresa ganhar dinheiro mudam muito mais devagar. As arquiteturas limpas — Hexagonal (Cockburn, 2005), Onion (Palermo, 2008) e Clean Architecture (Martin, 2012) — organizam o código para que o núcleo de negócio não dependa de nenhum detalhe técnico. Neste capítulo, vemos como a Regra da Dependência e o padrão Ports & Adapters transformam banco, web e mensageria em peças substituíveis.


🗺️ Mapa Conceitual do Capítulo

flowchart TD
    CLEAN["Arquiteturas Limpas e Desacopladas"]

    CLEAN --> RULE["1. Regra da Dependência (Uncle Bob)<br/>• Dependências de código apontam só para dentro<br/>• O núcleo não conhece frameworks<br/>• Inversão via interfaces (DIP)"]

    CLEAN --> ENT["2. Entidades & Casos de Uso (Core Puro)<br/>• Entidades: regras corporativas invariantes<br/>• Casos de uso: fluxos da aplicação<br/>• Testáveis sem banco, web ou rede"]

    CLEAN --> HEX["3. Hexagonal (Ports & Adapters)<br/>• Portas de entrada (driving) e saída (driven)<br/>• Adaptadores: HTTP, CLI, SQL, filas<br/>• Troca de tecnologia sem tocar no core"]

    style CLEAN fill:#ede7f6,stroke:#512da8,stroke-width:2px
    style RULE fill:#e8f5e9,stroke:#4caf50,stroke-width:1.5px
    style ENT fill:#fff3e0,stroke:#ff9800,stroke-width:1.5px
    style HEX fill:#e1f5fe,stroke:#03a9f4,stroke-width:1.5px

🧱 1. O Problema: Arquitetura em Camadas Acoplada ao Banco

Na arquitetura em camadas tradicional (Controller → Service → Repository → Banco), a dependência de código desce até a infraestrutura. O resultado típico:

  • As entidades de negócio são classes do ORM (class Pedido(db.Model)), carregando anotações e sessões do framework;
  • Testar uma regra simples de desconto exige subir um banco de dados;
  • Migrar de MySQL para PostgreSQL, ou de REST para mensageria, obriga a alterar a camada de serviço.

“The architecture should scream the intent of the system, not the frameworks it uses.” — Robert C. Martin, Clean Architecture (2017)

Olhando a estrutura de pastas de um bom sistema, deveríamos ler fechar_pedido, calcular_frete, emitir_nota — e não controllers, models, migrations.


🎯 2. A Regra da Dependência (The Dependency Rule)

“Source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle.” — Robert C. Martin

flowchart TD
    subgraph L4["Frameworks & Drivers (Web, DB, Devices)"]
        subgraph L3["Interface Adapters (Controllers, Gateways, Presenters)"]
            subgraph L2["Application Business Rules (Use Cases)"]
                subgraph L1["Enterprise Business Rules (Entities)"]
                    E["Entidades Puras"]
                end
                UC["Casos de Uso"]
            end
            ADP["Controladores & Repositórios"]
        end
        FW["HTTP / SQL / Redis / CLI"]
    end

    FW --> ADP --> UC --> E

    style L1 fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style L2 fill:#fff9c4,stroke:#fbc02d,stroke-width:2px
    style L3 fill:#ffe0b2,stroke:#f57c00,stroke-width:2px
    style L4 fill:#bbdefb,stroke:#1976d2,stroke-width:2px
Círculo Contém Muda quando… Pode importar
Entidades Regras corporativas (Conta.debitar, Pedido.total) A política da empresa muda Apenas a linguagem
Casos de Uso Fluxos da aplicação (TransferirDinheiro) O processo da aplicação muda Entidades + portas
Adaptadores Controllers, presenters, repositórios concretos O formato de entrada/saída muda Casos de uso
Frameworks & Drivers FastAPI, Spring, PostgreSQL, Kafka A tecnologia muda Tudo

2.1 Como o Fluxo Sai do Centro sem Violar a Regra?

O caso de uso precisa salvar dados, mas não pode importar o repositório SQL. A solução é o DIP (Capítulo 6): o núcleo declara uma interface e o adaptador externo a implementa. O fluxo de controle vai para fora, mas a dependência de código aponta para dentro:

# núcleo: declara o que precisa
class ContaRepositorioPort(ABC):
    @abstractmethod
    def obter(self, numero: str) -> Conta: ...

# borda: implementa com a tecnologia escolhida
class RepositorioSQLite(ContaRepositorioPort):
    def obter(self, numero):
        linha = self._db.execute("SELECT ... WHERE numero = ?", (numero,)).fetchone()
        return Conta(*linha)

⬡ 3. Arquitetura Hexagonal (Ports & Adapters)

Criada por Alistair Cockburn (2005), a Arquitetura Hexagonal descreve a mesma ideia com outra metáfora: a aplicação é um hexágono cujos lados são portas, e cada tecnologia se conecta por um adaptador.

flowchart LR
    subgraph Entrada["Adaptadores de Entrada (driving)"]
        HTTP["API HTTP"]
        CLI["CLI"]
        TST["Teste automatizado"]
    end
    subgraph Core["⬡ Núcleo da Aplicação"]
        IN(["Porta de entrada<br/>TransferirDinheiro"])
        DOM["Entidades<br/>Conta"]
        OUT1(["Porta de saída<br/>ContaRepositorioPort"])
        OUT2(["Porta de saída<br/>NotificacaoPort"])
        IN --> DOM
        IN --> OUT1
        IN --> OUT2
    end
    subgraph Saida["Adaptadores de Saída (driven)"]
        PG["PostgreSQL / SQLite"]
        MEM["Repositório em memória"]
        PUSH["Push / E-mail"]
    end
    HTTP --> IN
    CLI --> IN
    TST --> IN
    PG -.->|implementa| OUT1
    MEM -.->|implementa| OUT1
    PUSH -.->|implementa| OUT2
  • Portas de entrada (driving/primary): a API que o núcleo oferece ao mundo — normalmente os próprios casos de uso.
  • Portas de saída (driven/secondary): o que o núcleo precisa do mundo — persistência, notificações, pagamentos, relógio.
  • Adaptadores: traduzem entre o protocolo externo (JSON, SQL, AMQP) e as portas. Podem ser trocados em tempo de configuração, inclusive por versões em memória nos testes.

3.1 Hexagonal, Onion e Clean: Mesma Família

Aspecto Hexagonal (2005) Onion (2008) Clean (2012)
Metáfora Hexágono com portas Camadas de cebola Círculos concêntricos
Foco Simetria entre entrada e saída Domínio no centro, serviços ao redor Regra da Dependência explícita
Contribuição principal Vocabulário ports & adapters Domain Services Casos de uso e Presenters

⚖️ 4. Custos, Armadilhas e Pragmatismo

  • Cerimônia: para um CRUD simples, portas + adaptadores + DTOs podem triplicar o número de arquivos sem benefício. Reserve a arquitetura completa para domínios com regras ricas.
  • Mapeamento entre camadas: entidades de domínio e modelos do ORM são objetos diferentes, exigindo conversões (mappers).
  • “Arquitetura de pastas”: criar diretórios domain/ e infra/ não basta; se o domínio importa o ORM, a regra foi violada. Verifique automaticamente com ferramentas como import-linter (Python), ArchUnit (Java) e dependency-cruiser (JavaScript).
  • Transações: o limite transacional normalmente fica no caso de uso, exposto por uma porta Unit of Work, para não vazar sessões de banco para o núcleo.

💡 Regra prática: se você consegue rodar todos os testes dos casos de uso sem Docker, sem rede e sem banco, o núcleo está realmente isolado.