 ASP.NET 
Core - Implementando Onion Architecture com CQRS - II
ASP.NET 
Core - Implementando Onion Architecture com CQRS - II
|  | Hoje vamos continuar realizando a implementação da Onion Architecture em uma aplicação ASP .NET Core aplicando o CQRS. | 
Continuando a primeira parte do artigo vamos agora implementar a arquitetura Cebola em uma aplicação ASP .NET Core.
|   | 
Como exemplo vamos criar uma aplicação ASP .NET Core para gerenciar informações de Alunos acessando um banco de dados SQL Server via EF Core onde vamos implementar o padrão CQRS.
Os recursos usados serão os seguintes:
Criando a Solução e os projetos no VS 2019
Vamos iniciar criando uma Blank Solution no VS 2019 com o nome OnionArch;

A seguir crie 3 pastas na solução usando a opção Add-> New Solution Folder;

Agora vamos criar os projetos em cada pasta iniciando com a pasta Core.
Clique com o botão direito na pasta Core e selecione Add -> New Project e selecione Class Library e informe o nome Domain e selecione o framework atual .NET 5.0 :

Este projeto terá as seguintes definições :

Agora vamos repetir este procedimento e criar na pasta Core o projeto Application com as mesmas propriedades e a seguir criar na pasta Infrastructure o projeto Persistence;
Finalmente na pasta Presentation vamos criar um projeto do tipo Web API.
Clique com o botão direito na pasta Presentation e selecione Add -> New Project e selecione ASP.NET Core Web API ;

Informe o nome 
ApiAlunos e defina as propriedades conforme a figura baixo e clique em
Create;

Temos assim a nossa solução contendo os projetos criados usando a disposição recomendada pela Arquitetura Cebola:

Vamos agora definir os recursos em cada projeto da Solução seguindo as recomendações da arquitetura. Vamos iniciar com o projeto Domain da camada Core.
1- Domain
A camada Domain deve ser independente de todas as demais camadas e fatores externos e deve conter as regras do negócio e as entidades usadas para expressar o domínio.
Vamos criar neste projeto as pastas:
Na pasta Validation vamos criar uma classe DomainExceptionValidation que herda de Exception que iremos usar para validar o domínio:
| public class DomainExceptionValidation : Exception { public DomainExceptionValidation(string error) : base(error) { } 
		                public static void When(bool hasError, string error) | 
Vamos criar na pasta Entities uma classe abstrata chamada Entity onde vamos definir a propriedade Id que será herdada pela classe Aluno.
| public abstract class Entity { public int Id { get; protected set; } } | 
Para não complicar muito o exemplo vamos criar uma classe Aluno na pasta Entities e definir apenas 4 propriedades: Nome, Email, Curso e Nota.
Esta classe vai herdar da classe Entity e assim receberá o Id desta classe e iremos realizar a validação desta classe usando a classe DomainExceptionValidation.
| using Domain.Validation; 
		namespace Domain.Entities                         DomainExceptionValidation.When(nome.Length < 3,                         DomainExceptionValidation.When(string.IsNullOrEmpty(email),                         DomainExceptionValidation.When(email.Length < 3,                         DomainExceptionValidation.When(!email.Contains("@"),                         DomainExceptionValidation.When(string.IsNullOrEmpty(curso),                         DomainExceptionValidation.When(curso.Length < 5, DomainExceptionValidation.When(nota < 0, "Nota inválida"); DomainExceptionValidation.When(nota > 10, "Nota inválida");                                    Nome = nome; | 
Nosso modelo de domínio embora não seja um domínio rico não é totalmente anêmico.
Aqui definimos o seguinte:
Na pasta Interfaces vamos criar a interface IAlunoRepository :
| public interface IAlunoRepository { Task<IEnumerable<Aluno>> GetAlunosAsync(CancellationToken cancellationToken = default); Task<Aluno> GetAlunoIdAsync(int alunoId, CancellationToken cancellationToken = default); Task<Aluno> UpdateAsync(Aluno aluno); Task<Aluno> InsertAsync(Aluno aluno); Task<Aluno> RemoveAsync(Aluno aluno); } | 
Temos a definição de um contrato com métodos assíncronos que serão implementados no Repositório.
Mas afinal porque temos a definição de uma interface do repositório na camada de domínio ?
Isso ocorre porque os casos de uso no domínio não estão usando a implementação real do repositório que fica na camada de dados. Em vez disso, está apenas usando uma abstração/interface na Camada Domain que atua como um contrato para qualquer repositório que deseja fornecer os dados.
Observe que estamos definindo o argumento CancelamentoToken como um valor opcional e atribuindo a ele o valor padrão. Com essa abordagem, se não fornecermos um valor de CancellationToken, um CancellationToken.None será fornecido para nós. Fazendo isso, podemos garantir que nossas chamadas assíncronas que usam o CancelToken sempre funcionarão.
Ao final teremos a implementação da camada Domain :

2- Application
Esta camada também pode ser identificada como a camada de serviços, o nome não é o mais importante.
O que é importante é que esta camada fica logo acima da camada de Domínio, o que significa que tem uma referência à camada de Domínio. Nesta camada vamos criar as seguintes pastas:
Neste projeto iremos definir os DTOs , o CQRS, o Mapeamento do DTO e criar a a interface de serviço e sua implementação.
Assim teremos que incluir uma referência ao projeto Domain neste projeto e também vamos incluir neste projeto as seguintes referências:
Vamos iniciar criando o arquivo AlunoDto na pasta DTOs :
| using System; using System.ComponentModel.DataAnnotations; 
		namespace Application.DTOs                 [Required(ErrorMessage = "Informe o nome")]                 [Required(ErrorMessage = "Informe o email")]                 [Required(ErrorMessage = "Informe o curso")]                 [Required(ErrorMessage = "Informe a nota")] | 
Observe que aqui podemos definir os Data Annotations para validar os dados no input do usuário.
A seguir na pasta Mappings vamos criar o Mapeamento definindo a classe DomainToDtoMappingProfile usando os recursos do AutoMapper:
| using Application.DTOs;
using AutoMapper;
using Domain.Entities;namespace Application.Mappings
{
    public class DomainToDtoMappingProfile : Profile
    {
        public DomainToDtoMappingProfile()
        {
            CreateMap<Aluno, AlunoDto>().ReverseMap();
        }
    }
} | 
A seguir vamos criar a interface IAlunoService na pasta Interfaces para definir o serviço para gerenciar os alunos:
| using Application.DTOs; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; 
		namespace Application.Interfaces | 
Aqui já estamos usando o DTO AlunoDto nas definições do serviço.
Agora para poder implementar o CQRS e o Serviço para o Cliente precisamos criar o contexto e o repositório na camada Infrastructure.
2- Infrastructure
Nesta camada teremos as implementações e as referências ao Entity Framework Core e aqui iremos armazenar o Migrations.
Vamos criar nesta pasta os seguintes projetos:
No projeto Persistence vamos criar as seguintes pastas:

Neste projeto vamos incluir uma referência ao projeto Domain e as referências aos seguintes pacotes:
Na pasta Context vamos criar a classe de Contexto ApplicationDbContext que herda de DbContext:
| using Domain.Entities; using Microsoft.EntityFrameworkCore; 
		namespace Persistence.Context public DbSet<Aluno> Alunos { get; set; }                 protected override void OnModelCreating(ModelBuilder builder) | 
A seguir na pasta EntitiesConfiguration vamos criar a classe AlunoConfiguration onde vamos usar a Fluent API para configurar as propriedades da entidade Aluno para o EF Core:
| using Domain.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; 
		namespace Persistence.EntitiesConfiguration | 
Agora na pasta Repositories vamos implementar o nosso repositório criando a classe AlunoRepository que vai herdar da classe IAlunoRepository definida na camada Domain:
| using Domain.Entities; using Domain.Interfaces; using Microsoft.EntityFrameworkCore; using Persistence.Context; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; 
		namespace Persistence.Repositories                 public AlunoRepository(ApplicationDbContext alunoContext)                 public async Task<IEnumerable<Aluno>> GetAlunosAsync(CancellationToken cancellationToken = default)                 public async Task<Aluno> GetAlunoIdAsync(int alunoId, CancellationToken cancellationToken = default)                 public async Task<Aluno> InsertAsync(Aluno aluno)                 public async Task<Aluno> UpdateAsync(Aluno aluno)                 public async Task<Aluno> RemoveAsync(Aluno aluno) | 
Temos aqui o repositório que iremos usar para implementar os comandos e consultas no CQRS.
Na próxima parte do artigo iremos implementar o CQRS criando as consultas e os comandos usando o MediatR.
"Cheguemos, pois, com confiança 
	ao trono da graça, para que possamos alcançar misericórdia e achar graça, a 
	fim de sermos ajudados em tempo oportuno."
	Hebreus 4:16
Referências:
C# - Lendo e escrevendo em arquivos textos e binários
C# - Entendo o I/O na plataforma .NET
C# - Fluxo assíncrono ou async streams