DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

Descomplicando a Clean Architecture (Arquitetura Limpa)

Introdução

Clean Architecture é uma arquitetura que mantém o foco no domain. Seu objetivo principal é isolar e manter independência do domínio. A Clean propõe que o domínio (domain) nunca deve depender de detalhes externos.

O conceito foi baseado nas boas práticas e nas arquitetureas hexagonal e cebola, dentre outras, que já propunham a separação das responsabilidades em camadas e tinham como objetivo produzir sistemas com as seguintes características.

  • Independente de framework
  • Testáveis
  • Independentes da interface do usuário
  • Independentes do banco de dados
  • Independente de qualquer agente externo

Isto é:

  • Se amanhã trocar a interface (Web → Mobile), a regra de negócio continuam a mesma.
  • Banco de dados, controllers, bibliotecas externas e frameworks são considerados plugáveis. Desse modo, há um desacoplamento entre a infraestrutura
  • Testabilidade: como o domínio não depende de frameworks e banco de dados, você pode testar os casos de usos em memória, sem precisar rodar a infra.

Camadas

A princípio essa arquitetura possui 4 camadas mínimas, o número suficiente manter o isolamento funcional (apesar de não haver restrição de número máximo).

Entidades (Entities)

No centro da arquitetura, temos as classes responsáveis pelas regras de negócios, que podem ser dois tipos: Entidades e Casos de Uso.

Entidade: conjunto de regras de negócios relacionadas que são críticas para o funcionamento do aplicativo.

  • As regras seriam agrupadas como métodos em uma classe
  • As regras existem mesmo se não existir a aplicação

Como as entidades não conhecem as outras camadas elas não dependem de nada

Casos de uso (Use cases)

Nessa camada contém as regras de negócio específica do aplicativo. Dizem como automatizar o sistema determinando o seu comportamento.

  • Orquestram o fluxo de e para as entidades
  • Direcionam as entidades a usar as regras de negócios.

Alguns exemplos de casos de usos são:

  • Obter informação de um produto: nome, preço, quantidade…
  • Montar catálogo de produtos
  • Verificar estoque

Como vimos na imagem acima, os casos de usos interagem e dependem das entidades, mas não sabem nada mais sobre as camadas mais distantes.

Adptadores (Adpters)

Na terceira camada a partir do centro, temos classes e interfaces chamadas de Adaptadores. A função dela é converter os dados de um formato para outro. São os tradutores entre domínio e a infraestrutura.

Eles convertem os dados do formato mais convenientes para os casos de usos e entidades

Esssa é a camada que vai conter a implementação MVC de uma UI com apresentadores, vizualizações e controladores, ou que poderá realizar a implementação dos endpoints de uma API REST

Nenhum código dentro desse círculo deve saber absolutamente nada sobre o banco de dados

Camada mais externa

Essa camada é para onde vão todos os componentes de entrada ou saída (input, output), a interface com o usuário (UI), o banco de dados, os frameworks, os dispositivos, etc.

Essa camada é para onde vão todos os detalhes como as interfaces e banco de dados. Ela é a camada mais volátil, pois pode mudar com frequência. E por isso, essa camada é mantida o mais longe possível das camadas de domínio.

Como elas são mantidas separadas, é fácil fazer alterações ou trocar um componente por outro. (por exemplo UI, banco de dados, estruturas e dispositivos)

Considerações finais das camadas

Desse modo, podemos dizer que

As camadas internas são mais estáveis

As camadas externas são mais sujeitas a mudanças

As entidades raramente devem ser modificadas

Alterações nos Use Cases não devem ser motivadas por mudanças na tecnologia, banco de dados, frameworks, etc.

A Regra de dependência garante que as entidades e os casos de usos sejam classes limpas de qualquer tecnologia ou serviço externo ao sistema

The Dependency Rule

The Dependency Rule. Essa é a regra de ouro dessa arquitetura: Dependências sempre apontam para dentro. Ou seja, a camada interna nunca depende de uma externa.

Relação da Clean com o DDD

A Clean Architecture não é um substituto do DDD, ela é na verdade uma arquitetura que facilita o DDD.

  • O Domínio do DDD (Entidades, Objetos de Valor, Agregados) está na camada de Entities da Clean
  • Os Casos de Usos/Application Services do DDD estão na camadas de Use Cases
  • Os repositórios, Gateways e Adapters (interfaces do comunicação com o mundo externo) ficam nas camadas externas.

Desse modo, podemos afirmar que o DDD define o que modelar (o negócio), e a Clean define como organizar (a arquitetura protege o negócio)

Clean na prática com .NET

Como já vimos na teoria tudo começa pelo domínio, e as dependências sempre apontam para dentro. O fluxo existe para garantir que as dependências sempre apontem para dentro, mas nunca ao contrário. É o que Uncle Bob chama de Dependency Rule. Então, abaixo mostrei o fluxo de execução conceitual e das dependências.

Fluxo conceitual (de execução)

Quando o sistema roda, o fluxo natural de execução é:

Usuário  API  Application  Domain  Infrastructure
Enter fullscreen mode Exit fullscreen mode

ou, em palavras:

  1. O usuário chama um endpoint (ex: POST /pedidos).
  2. O Controller (camada API) recebe a requisição e aciona o Use Case.
  3. O Use Case orquestra as regras e cria entidades do Domain.
  4. O Domain aplica suas regras, invariantes e validações.
  5. O Use Case pode então chamar um repositório (definido como interface no Application), cuja implementação concreta está na Infrastructure (ex: gravação no banco).

Então o fluxo de execução vai “pra baixo” — mas o fluxo de dependência vai pra dentro.

O fluxo das dependências (de código-fonte)

Agora o segredo:

Mesmo que o fluxo de execução vá da API → Infra, o código e as referências entre projetos apontam no sentido oposto:

MyApp.Api ──→ MyApp.Application ──→ MyApp.Domain
                    
                    
         MyApp.Infrastructure (implementa interfaces)
Enter fullscreen mode Exit fullscreen mode
  • A API conhece a Application, porque chama os Use Cases.
  • A Application conhece a Domain, porque usa as Entidades.
  • Mas nenhuma das duas conhece a Infrastructure

    apenas definem interfaces (ex: IPedidoRepository).

  • A Infrastructure é quem conhece as outras, porque precisa:

    • Implementar IPedidoRepository;
    • Registrar as implementações no container de injeção (DependencyInjection).

então, resumidamente, as dependências possuem esse tipo de papel:

Camada Papel Depende de
Domain Regras puras (núcleo) Ninguém
Application Usa as regras do domínio Domain
API (ou UI) Mostra o sistema pro mundo Application
Infrastructure Conecta o sistema ao mundo real Domain + Applicatio

Estrutura típica de um projeto .NET

Geralmente a solução .sln fica assim:

CleanOrders/

├── src/
   ├── CleanOrders.Domain/
   ├── CleanOrders.Application/
   ├── CleanOrders.Infrastructure/
   └── CleanOrders.Api/

├── tests/
   ├── CleanOrders.UnitTests/
   └── CleanOrders.IntegrationTests/

└── CleanOrders.sln
Enter fullscreen mode Exit fullscreen mode

O projeto de saída deve ser do tipo ASP.NET Core Web App (ou outro tipo de projeto executável compatível). Os demais projetos da solução devem ser configurados como Class Library, de modo que sirvam como bibliotecas de apoio e não como pontos de entrada da aplicação.
Vamos destrinchar toda essa estrutura?

1. Domain (Camada mais interna)

Aqui é o coração do sistema, onde tem as regras de negócios puras. Sem referência externa.

CleanOrders.Domain/

├── Entities/
   ├── Pedido.cs
   └── ItemPedido.cs

├── ValueObjects/
   └── Email.cs

├── Enums/
   └── StatusPedido.cs

├── Exceptions/
   └── DomainException.cs

└── Common/
    └── EntityBase.cs
Enter fullscreen mode Exit fullscreen mode

2. Application (Casos de uso / Regras de aplicação)

Orquestra o domínio e depende apenas dele

CleanOrders.Application/

├── Interfaces/
   └── IPedidoRepository.cs

├── DTOs/
   ├── PedidoDto.cs
   └── ItemDto.cs

├── Features/
   ├── Pedidos/
      ├── Commands/
         ├── CriarPedido/
            ├── CriarPedidoCommand.cs
            ├── CriarPedidoCommandHandler.cs
            └── CriarPedidoValidator.cs
         └── FinalizarPedido/
             ├── FinalizarPedidoCommand.cs
             └── FinalizarPedidoHandler.cs
      └── Queries/
          └── GetPedidoById/
              ├── GetPedidoByIdQuery.cs
              └── GetPedidoByIdHandler.cs
   
   └── Common/
       └── PaginatedList.cs

└── DependencyInjection.cs
Enter fullscreen mode Exit fullscreen mode

3. Infraestructure (Implementações técnicas)

Implementa interfaces da camada application. Contém EF Core, Dapper, serviços externosm, etc.

CleanOrders.Infrastructure/

├── Persistence/
   ├── CleanOrdersDbContext.cs
   ├── Configurations/
      ├── PedidoConfiguration.cs
      └── ItemPedidoConfiguration.cs
   └── Migrations/

├── Repositories/
   └── PedidoRepository.cs

├── Services/
   └── EmailSender.cs

└── DependencyInjection.cs
Enter fullscreen mode Exit fullscreen mode

4. API (Camada de apresentação / interface externa)

Exposição via HTTP (REST) ou Interface da aplicação

CleanOrders.Api/

├── Controllers/
   └── PedidosController.cs

├── Middlewares/
   └── ExceptionHandlingMiddleware.cs

├── Filters/
   └── ValidationFilter.cs

├── appsettings.json
├── Program.cs
└── DependencyInjection.cs
Enter fullscreen mode Exit fullscreen mode

Testes (Unitários e de integração)

tests/
├── CleanOrders.UnitTests/
   └── Application/
       └── Pedidos/
           └── CriarPedidoCommandHandlerTests.cs
└── CleanOrders.IntegrationTests/
└── Api/
└── PedidosEndpointsTests.cs
Enter fullscreen mode Exit fullscreen mode

Top comments (0)