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