Arquitetura Limpa e sua Estrutura de Pastas em C#

banner-arquitetura-limpa-e-sua-estrutura-de-pastas-em-csharp

arquitetura limpa é uma abordagem para estruturar sua aplicação em camadas que separam as diferentes responsabilidades do sistema. Ela foi proposta por Robert C. Martin, também conhecido como Uncle Bob, e tem como objetivo tornar o código mais fácil de entender, testar, manter e evoluir.

 

A ideia principal da arquitetura limpa é que as dependências do código devem apontar para dentro, ou seja, as camadas mais internas não devem saber nada sobre as camadas mais externas. Isso permite que o núcleo do sistema, que contém as regras de negócio e de aplicação, fique isolado de detalhes de infraestrutura, como banco de dados, frameworks, UI, etc.

Neste artigo, vamos ver como podemos aplicar a arquitetura limpa em um projeto .NET Core, usando C# como linguagem de programação. Vamos criar uma solução com quatro projetos, cada um representando uma camada da arquitetura:

  • Domain: contém as entidades, objetos de valor, eventos de domínio, exceções e interfaces de repositório. É a camada mais interna e não depende de nenhuma outra.
  • Application: contém os casos de uso, serviços, comandos, consultas e eventos da aplicação. É a camada que orquestra as regras de domínio e depende apenas da camada de domínio.
  • Infrastructure: contém as implementações concretas das interfaces definidas nas camadas internas, como repositórios, serviços de email, mensageria, etc. É a camada que lida com os detalhes de infraestrutura e depende das camadas internas.
  • Presentation: contém os controladores que definem os endpoints da API do sistema. É a camada que lida com a interação com o usuário e depende das camadas internas.

Vamos ver como podemos criar essa estrutura de pastas usando um template do .NET Core.

Criando a solução Para criar a solução usando a arquitetura limpa, vamos usar um template disponível no GitHub1. Esse template já vem com alguns exemplos de código e testes para nos ajudar a entender melhor como funciona a arquitetura.

Para instalar o template, precisamos executar o seguinte comando no terminal:

dotnet new -i Ardalis.CleanArchitecture.Template

Depois de instalado, podemos navegar até o diretório onde queremos criar a solução e executar o seguinte comando:

dotnet new clean-arch -o NomeDoProjeto

Isso vai criar uma pasta chamada NomeDoProjeto com a seguinte estrutura:

📁 NomeDoProjeto 

|__ 📁 src 

|__ 📁 Application 

|__ 📁 Domain 

|__ 📁 Infrastructure 

|__ 📁 WebUI

|__ 📁 tests 

|__ 📁 Application.UnitTests 

|__ 📁 Domain.UnitTests 

|__ 📁 Infrastructure.IntegrationTests 

|__ 📁 WebUI.IntegrationTests 

|__ #️⃣ NomeDoProjeto.sln

Podemos ver que cada projeto corresponde a uma camada da arquitetura e que há também projetos de teste para cada camada. Além disso, há um arquivo .sln que representa a solução completa.

Para abrir a solução no Visual Studio Code, podemos executar o seguinte comando na pasta raiz:

code .

Isso vai abrir o VS Code com todos os projetos carregados. Podemos ver também que há alguns arquivos README.md em cada projeto, explicando o propósito e o conteúdo de cada um.

Vamos analisar cada projeto em mais detalhes.

Projeto Domain O projeto Domain é o mais simples e contém apenas uma classe chamada ToDoItem.cs na pasta Entities. Essa classe representa uma entidade do nosso domínio, que é uma tarefa a ser feita. Ela tem as seguintes propriedades:

  • Id: um identificador único do tipo Guid.
  • Title: um título descritivo do tipo string.
  • Description: uma descrição opcional do tipo string.
  • Done: um indicador se a tarefa foi concluída ou não do tipo bool.
  • Priority: uma enumeração que indica a prioridade da tarefa, podendo ser Low, Medium ou High.
  • Created: uma data e hora que indica quando a tarefa foi criada do tipo DateTime.

Além disso, a classe tem um método chamado MarkComplete, que marca a tarefa como concluída e atualiza a propriedade Done.

O projeto Domain também contém uma interface chamada IToDoItemRepository.cs na pasta Repositories. Essa interface define os métodos que um repositório de tarefas deve implementar, como:

  • ListAsync: retorna uma lista de todas as tarefas do tipo IReadOnlyList<ToDoItem>.
  • AddAsync: adiciona uma nova tarefa do tipo ToDoItem e retorna o Id gerado do tipo Guid.
  • GetByIdAsync: retorna uma tarefa pelo Id do tipo ToDoItem.
  • UpdateAsync: atualiza uma tarefa existente do tipo ToDoItem.
  • DeleteAsync: deleta uma tarefa pelo Id do tipo bool.

Essa interface é usada pela camada de aplicação para abstrair os detalhes de persistência das tarefas. A implementação concreta dessa interface fica na camada de infraestrutura.

O projeto Domain não tem nenhuma dependência externa, apenas o pacote Microsoft.NETCore.App.

Projeto Application O projeto Application é o mais complexo e contém vários arquivos e pastas. Vamos ver cada um deles.

Na pasta Behaviors, há duas classes chamadas LoggingBehavior.cs e PerformanceBehavior.cs. Essas classes são exemplos de middlewares que interceptam os pedidos da camada de apresentação e executam alguma lógica antes ou depois dos casos de uso. Por exemplo, o LoggingBehavior registra os detalhes do pedido e da resposta no console, e o PerformanceBehavior mede o tempo de execução do caso de uso e registra um aviso se for maior que um limite definido.

Na pasta Common, há duas classes chamadas AuditableEntity.cs e BaseEntity.cs. Essas classes são usadas como classes base para as entidades do domínio, adicionando algumas propriedades comuns, como Id, CreatedBy, Created, LastModifiedBy e LastModified. Essas propriedades são usadas para rastrear as alterações nas entidades e podem ser preenchidas automaticamente usando o EF Core.

Na pasta Contracts, há duas interfaces chamadas ICurrentUserService.cs e IDateTime.cs. Essas interfaces são usadas para abstrair os serviços externos que a camada de aplicação precisa usar, como obter o usuário atual ou a data e hora atual. As implementações dessas interfaces ficam na camada de infraestrutura.

Na pasta Exceptions, há uma classe chamada NotFoundException.cs. Essa classe é usada para representar uma exceção personalizada que indica que um recurso não foi encontrado. Ela recebe um nome e um Id do recurso e constrói uma mensagem adequada.

Na pasta Features, há duas pastas chamadas ToDoItems e WeatherForecast. Cada pasta representa um recurso da aplicação e contém os casos de uso relacionados a ele. Cada caso de uso é representado por três arquivos: um comando ou consulta, um manipulador e um modelo de resposta.

Por exemplo, na pasta ToDoItems há o caso de uso CreateToDoItem. Ele é composto pelos seguintes arquivos:

  • CreateToDoItemCommand.cs: define o comando para criar uma nova tarefa, contendo as propriedades Title, Description e Priority.
  • CreateToDoItemCommandHandler.cs: define o manipulador do comando, que recebe uma instância do comando e uma instância do repositório de tarefas. Ele valida o comando, cria uma nova entidade ToDoItem, adiciona ao repositório e retorna o Id gerado.
  • CreateToDoItemCommandResponse.cs: define o modelo de resposta do comando, contendo apenas a propriedade Id.

Os outros casos de uso seguem a mesma estrutura, mas com diferentes comandos, consultas, manipuladores e modelos de resposta.

O projeto Application depende apenas do projeto Domain e dos pacotes MediatR (para implementar o padrão mediator), FluentValidation (para validar os comandos e consultas) e Microsoft.NETCore.App.

Projeto Infrastructure O projeto Infrastructure é o responsável por implementar as interfaces definidas nas camadas internas e lidar com os detalhes de infraestrutura. Ele contém os seguintes arquivos e pastas:

Na pasta Data, há duas classes chamadas ApplicationDbContext.cs e ApplicationDbContextFactory.cs. A primeira classe representa o contexto do banco de dados usando o EF Core. Ela herda da classe IdentityDbContext para usar a autenticação baseada em identidade do ASP.NET Core. Ela também implementa a interface IApplicationDbContext para expor os DbSets das entidades do domínio.