Pular para o conteúdo
Categoria: Fundamentos & Boas Práticas13 min de leitura

O que é Arquitetura Limpa (Clean Architecture)?

Por Schematize Blog ·

Entenda as camadas da Clean Architecture, a regra de dependência e como isolar as regras de negócio de frameworks, bancos de dados e detalhes externos, com exemplos práticos.

Frameworks mudam, bancos de dados são trocados, interfaces gráficas são reescritas — mas as regras de negócio de um sistema tendem a durar muito mais. A Arquitetura Limpa (Clean Architecture) é uma abordagem de organização de software que coloca essas regras no centro e empurra os detalhes técnicos para a periferia. Neste artigo você vai entender suas camadas, a regra de dependência que sustenta tudo, como aplicá-la na prática com código e quais armadilhas evitar.

O que é Arquitetura Limpa?

A Arquitetura Limpa foi sistematizada por Robert C. Martin (Uncle Bob) em seu livro Clean Architecture (Martin, 2017). Ela não é um framework nem uma tecnologia, mas um conjunto de princípios para estruturar sistemas de modo que as regras de negócio fiquem independentes de detalhes como banco de dados, interface de usuário e bibliotecas externas.

A motivação é simples e profunda: o que dá valor a um software são suas regras de negócio, não o framework web ou o ORM que ele usa. Se a arquitetura amarra as regras de negócio a esses detalhes, qualquer mudança tecnológica vira um terremoto. A Arquitetura Limpa inverte essa relação de dependência.

Martin parte de uma observação histórica: várias arquiteturas — Arquitetura Hexagonal (Ports and Adapters), Onion Architecture, DCI, BCE — chegaram independentemente às mesmas conclusões. A Clean Architecture é uma síntese dessas ideias em torno de uma única regra central.

Política e detalhe

Há uma distinção que organiza todo o raciocínio de Martin: a diferença entre política e detalhe. Política é a regra de negócio, o motivo pelo qual o software existe — calcular um juro, validar um pedido, aplicar um desconto. Detalhe é o meio pelo qual essa política é entregue ou armazenada — o banco de dados, o framework web, o formato de tela.

A tese central é que a política não deveria depender do detalhe. O banco de dados é um detalhe. A web é um detalhe de entrega. Se o seu código de regra de negócio sabe que existe um PostgreSQL ou um servidor HTTP, você acoplou a parte estável e valiosa do sistema à parte volátil e substituível. A Arquitetura Limpa é, em essência, a disciplina de manter essa distinção sempre clara.

As camadas da Clean Architecture

A Arquitetura Limpa é frequentemente representada como círculos concêntricos. Cada anel é uma camada, e quanto mais ao centro, mais abstrato e estável é o código.

1. Entidades (Entities)

No centro estão as entidades: os objetos que encapsulam as regras de negócio mais gerais e de mais alto nível, independentes de qualquer aplicação específica. Uma entidade Conta que sabe calcular juros é regra de negócio pura — ela seria verdadeira mesmo sem nenhum sistema de computador.

2. Casos de uso (Use Cases)

A camada seguinte contém os casos de uso: as regras de negócio específicas da aplicação. Eles orquestram o fluxo de dados de e para as entidades para realizar uma operação concreta, como "transferir dinheiro" ou "registrar pedido". Os casos de uso dependem das entidades, mas não sabem nada sobre banco de dados ou HTTP.

3. Adaptadores de interface (Interface Adapters)

Aqui ficam os adaptadores que convertem dados entre o formato conveniente para os casos de uso e o formato conveniente para agentes externos. Controllers, presenters, gateways e repositórios concretos vivem nessa camada. É ela que traduz o "mundo de dentro" para o "mundo de fora" e vice-versa.

4. Frameworks e drivers (Frameworks & Drivers)

No anel mais externo estão os detalhes: o framework web, o banco de dados, a interface gráfica, dispositivos, serviços externos. Para a Clean Architecture, tudo isso é "detalhe" — coisas que podem e vão mudar, e que devem ficar o mais longe possível do núcleo.

O número de camadas não é sagrado

Os quatro anéis são uma referência, não uma lei. Martin é explícito ao dizer que pode haver mais ou menos camadas dependendo do sistema. O que não muda é a direção das dependências. Um sistema simples pode ter três camadas; um complexo pode ter cinco. O erro é tratar os quatro círculos como um número mágico a ser reproduzido em toda parte, em vez de focar no princípio que eles ilustram.

A regra de dependência: o coração da arquitetura

A ideia mais importante da Clean Architecture é a regra de dependência: as dependências de código-fonte devem apontar apenas para dentro, em direção a políticas de mais alto nível.

Isso significa que:

    Em outras palavras, os casos de uso não importam o framework web; é o framework web que depende dos casos de uso. As regras de negócio não sabem se os dados vêm de PostgreSQL, de um arquivo ou de uma API — elas apenas pedem dados através de interfaces.

    Dependência de código versus fluxo de controle

    Um ponto que confunde quem está começando é a diferença entre dependência de código-fonte e fluxo de controle em tempo de execução. Em tempo de execução, o controle muitas vezes vai de dentro para fora: um caso de uso precisa, sim, acabar chamando o banco de dados. O que a regra de dependência exige é que a dependência de código — quem importa quem, quem conhece o nome de quem — aponte sempre para dentro.

    Isso parece um paradoxo, mas é resolvido pela inversão de dependência, descrita a seguir. O caso de uso chama o banco em tempo de execução, mas no código ele só conhece uma interface que pertence à sua própria camada. Quem implementa essa interface é a camada externa, que depende da interna — não o contrário.

    Como inverter dependências entre camadas

    Quando um caso de uso (interno) precisa chamar algo externo, como salvar no banco, surge um aparente conflito: a dependência precisaria apontar para fora. A solução é o princípio da inversão de dependência, um dos cinco princípios do SOLID.

    O caso de uso define uma interface (porta) do que ele precisa, e a implementação concreta fica na camada externa:

    // Camada de caso de uso (interno) — define a abstração
    interface RepositorioDePedidos {
      salvar(pedido: Pedido): Promise<void>
      buscarPorId(id: string): Promise<Pedido | null>
    }
    
    class RegistrarPedido {
      constructor(private repo: RepositorioDePedidos) {}
    
      async executar(dados: DadosPedido): Promise<void> {
        const pedido = Pedido.criar(dados) // regra de negócio
        await this.repo.salvar(pedido)
      }
    }
    // Camada externa — implementa a abstração
    class RepositorioPedidosPostgres implements RepositorioDePedidos {
      async salvar(pedido: Pedido): Promise<void> {
        // detalhes de SQL aqui, isolados do núcleo
      }
      async buscarPorId(id: string): Promise<Pedido | null> {
        // ...
      }
    }

    Repare que RegistrarPedido não conhece o Postgres. Ele depende apenas da interface RepositorioDePedidos, que pertence à camada interna. A dependência de código-fonte aponta para dentro, mesmo que o fluxo de controle, em tempo de execução, vá para fora.

    Essa técnica é viabilizada pela injeção de dependência, que fornece a implementação concreta ao caso de uso sem que ele precise instanciá-la diretamente. A injeção de dependência é, na prática, o mecanismo que faz a regra de dependência funcionar.

    Atravessando as fronteiras

    Quando dados cruzam as fronteiras entre camadas, eles devem fazê-lo em estruturas simples e isoladas — DTOs (Data Transfer Objects) ou structs básicas. Você não deve passar uma entidade do banco de dados (com anotações do ORM, por exemplo) diretamente para um caso de uso, porque isso acoplaria o núcleo a um detalhe externo.

    A regra prática: nada que cruze uma fronteira para dentro deve carregar conhecimento da camada externa. Cada camada fala sua própria linguagem de dados e os adaptadores fazem a tradução.

    Um exemplo de fluxo completo

    Para tornar concreto, acompanhe um pedido HTTP atravessando as camadas até o banco e de volta:

    1. Controller (adaptador)   recebe o JSON da requisição HTTP
    2. Controller               monta um DTO simples com os dados
    3. Caso de uso              recebe o DTO, cria/usa entidades, aplica regras
    4. Caso de uso              chama a porta (interface) de repositório
    5. Repositório concreto     (externo) traduz para SQL e persiste
    6. Caso de uso              devolve um resultado em estrutura simples
    7. Presenter (adaptador)    formata o resultado como JSON de resposta

    Em nenhum momento o caso de uso tocou em JSON, HTTP ou SQL. Ele recebeu dados simples, aplicou política e devolveu dados simples. Toda a tradução para o mundo externo aconteceu nos adaptadores. Esse isolamento é o que permite testar o passo 3 e 4 sem subir nenhum servidor nem banco.

    Benefícios da Arquitetura Limpa

      Testabilidade na prática

      O benefício de testabilidade merece um exemplo, porque é o retorno mais imediato da arquitetura. Como o caso de uso depende de uma interface, você pode injetar uma implementação falsa em memória nos testes:

      // Implementação falsa para teste — sem banco, sem rede
      class RepositorioPedidosFake implements RepositorioDePedidos {
        public salvos: Pedido[] = []
        async salvar(pedido: Pedido) { this.salvos.push(pedido) }
        async buscarPorId(id: string) {
          return this.salvos.find(p => p.id === id) ?? null
        }
      }
      
      test('registra um pedido aplicando a regra de negócio', async () => {
        const repo = new RepositorioPedidosFake()
        const caso = new RegistrarPedido(repo)
      
        await caso.executar({ cliente: 'c-1', itens: [/* ... */] })
      
        expect(repo.salvos).toHaveLength(1)
      })

      Esse teste roda em milissegundos, não depende de infraestrutura e testa exatamente a política. É o tipo de teste que você consegue ter aos milhares sem que a suíte fique lenta — algo praticamente impossível quando as regras estão coladas ao banco.

      Relação com outros conceitos

      A Arquitetura Limpa não vive isolada. Ela conversa diretamente com vários outros temas de design.

      SOLID e Design Patterns

      Os cinco princípios do SOLID — especialmente a inversão de dependência e a segregação de interfaces — são a base teórica das fronteiras da Clean Architecture. Na implementação, vários design patterns essenciais aparecem naturalmente: Repository para isolar persistência, Factory para criar entidades, Adapter para traduzir entre formatos e Strategy para variar comportamentos.

      Domain-Driven Design

      A camada de entidades e casos de uso é onde o Domain-Driven Design (DDD) brilha. DDD oferece o vocabulário e os padrões táticos — agregados, value objects, serviços de domínio — para modelar o núcleo, enquanto a Clean Architecture oferece a estrutura macro que protege esse núcleo. Eric Evans já defendia isolar o domínio dos detalhes de infraestrutura por meio de uma arquitetura em camadas (Evans, 2003); a Clean Architecture formaliza e radicaliza essa ideia com a regra de dependência.

      Microsserviços

      A Clean Architecture é uma arquitetura interna a um serviço. Ela é independente da decisão de adotar microsserviços, que é uma escolha de arquitetura de implantação. Na prática, elas se combinam bem: cada microsserviço pode ter, internamente, suas próprias camadas limpas, mantendo o domínio isolado dos detalhes de comunicação e persistência.

      Arquitetura Hexagonal

      Vale uma nota sobre a Arquitetura Hexagonal (Ports and Adapters), proposta por Alistair Cockburn. Ela é uma das fontes diretas da Clean Architecture e usa um vocabulário muito parecido: "portas" são as interfaces que o núcleo define, e "adaptadores" são as implementações concretas que conectam o núcleo ao mundo externo. Se você entendeu a regra de dependência aqui, já entendeu o essencial da Hexagonal — as duas são primas, enfatizando o mesmo isolamento do domínio.

      Erros comuns ao adotar Clean Architecture

        Como detectar violações cedo

        Os erros acima são silenciosos: o código compila, os testes passam, e só meses depois você percebe que o núcleo está acoplado a um framework. Algumas práticas ajudam a pegá-los cedo:

          Por onde começar

            Perguntas frequentes

            Clean Architecture serve para qualquer projeto? Não. Em sistemas pequenos, com pouca regra de negócio e muito CRUD, o custo das abstrações pode superar o benefício. Ela brilha onde há lógica de domínio relevante e expectativa de vida longa.

            É a mesma coisa que Arquitetura Hexagonal? São muito próximas. A Hexagonal (Ports and Adapters) é uma das inspirações da Clean Architecture; ambas defendem isolar o domínio atrás de interfaces. A Clean apenas organiza isso em mais camadas nomeadas.

            Preciso de quatro camadas exatas? Não. Quatro é a ilustração clássica, mas o número real depende do sistema. O inegociável é a direção das dependências, não a contagem de anéis.

            Não é abstração demais? Pode ser, se aplicada sem critério. O risco real existe. A defesa é o bom senso: aplique separação proporcional à complexidade, e não por ritual.

            Conclusão

            A Arquitetura Limpa é, em essência, uma única regra disciplinada: as dependências apontam para dentro, em direção às regras de negócio. Ao isolar o domínio de frameworks, bancos e interfaces, você ganha um sistema testável, flexível e resistente à passagem do tempo e das modas tecnológicas. Ela não é gratuita — exige disciplina e bom senso para não cair em excesso de abstração — mas, em sistemas com regras de negócio relevantes, o retorno em manutenibilidade compensa amplamente. Comece protegendo seu núcleo, defina boas fronteiras e deixe os detalhes onde eles pertencem: na periferia.

            Referências

              Leituras relacionadas

              Nenhum comentário ainda

              Seja o primeiro a comentar.

              Deixe seu comentário

              Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

              Entrar com Canverly