ASP.NET Core - Implementando Onion Architecture com CQRS - III


Hoje vamos realizar a implementação da Onion Architecture em uma aplicação ASP .NET Core aplicando o CQRS.

Continuando a segunda parte do artigo vamos agora implementar os comandos e consultas segundo o CRQS usando o MediatR.

Implementando o CQRS com MediatR

O acrônimo CQRS significa -  Command Query Responsibility Segregation - e é um padrão de projeto que separa as operações de leitura e escrita da base de dados em dois modelos: Queries e Commands.

Os Commands são responsáveis pelas ações que realizam alguma alteração na base de dados e em geral são operações assíncronas sem retorno.

As Queries são responsáveis pelas consultas, e retornam objetos DTOs (Data Transfer Object).

O MediatR é uma biblioteca aberta criada por Jimmy Bogard , e, o seu objetivo é facilitar a implementação do Padrão Mediator  em aplicações .NET. Com ela não precisamos nos preocupar com a criação da classe mediadora, pois já são fornecidas interfaces que facilitam a implementação do fluxo de comunicação entre os objetos.

A criação de Commands usando o MediatR é composta por dois objetos:

Veja o meu artigo sobre o padrão MediatR : Usando o padrão Mediator com MediatR (CQRS)

Vamos realizar esta implementação no projeto Application onde criamos a pasta CQRS, e, agora dentro desta pasta, vamos criar duas pastas:

1- Commands

Na pasta Commands crie a classe CreateAlunoCommand

using Domain.Entities;
using Domain.Interfaces;
using MediatR;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Application.CQRS.Commands
{
    public class CreateAlunoCommand : IRequest<Aluno>
    {
        public string Nome { get; set; }
        public string Email { get; set; }
        public string Curso { get; set; }
        public int Nota { get; set; }

        public class CreateAlunoCommandHandler : IRequestHandler<CreateAlunoCommand, Aluno>
        {

            private readonly IAlunoRepository _alunoRepository;
            public CreateAlunoCommandHandler(IAlunoRepository alunoRepository)
            {
                _alunoRepository = alunoRepository;
            }

            public async Task<Aluno> Handle(CreateAlunoCommand request,
                CancellationToken cancellationToken)
            {
                var aluno =
new Aluno(request.Nome, request.Email, request.Curso,
                                  request.Nota);

                if (aluno == null)
                {
                    throw new ApplicationException($"Error creating entity.");
                }
                else
                {
                    return await _alunoRepository.InsertAsync(aluno);
                }
            }
        }
    }
}

Observe que nesta implementação usamos o recurso das classes internas ou inner class para implementar o Handler em CreateAlunoCommandHandler, onde no seu construtor injetamos uma instância do Repositório que criamos no projeto Infrastructure, e, implementamos o método Handle que cria um novo aluno.

A seguir vamos criar a classe UpdateAlunoCommand :

using Domain.Entities;
using Domain.Interfaces;
using MediatR;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Application.CQRS.Commands
{
    public class UpdateAlunoCommand : IRequest<Aluno>
    {
        public int Id { get; set; }
        public string Nome { get; set; }
        public string Email { get; set; }
        public string Curso { get; set; }
        public int Nota { get; set; }

        public class UpdateAlunoCommandHandler : IRequestHandler<UpdateAlunoCommand, Aluno>
        {
           
private readonly IAlunoRepository _alunoRepository;
            public UpdateAlunoCommandHandler(IAlunoRepository alunoRepository)
            {
                _alunoRepository = alunoRepository ??
                throw new ArgumentNullException(nameof(alunoRepository));
            }

            public async Task<Aluno> Handle(UpdateAlunoCommand request,
                CancellationToken cancellationToken)
            {
                var aluno = await _alunoRepository.GetAlunoIdAsync(request.Id);

                if (aluno == null)
                {
                    throw new ApplicationException($"Entity could not be found.");
                }
                else
                {
                    aluno.Update(request.Nome, request.Email, request.Curso,
                                    request.Nota);

                    return await _alunoRepository.UpdateAsync(aluno);
                }
            }
        }
    }
}

Esta implementação segue a mesma lógica do comando anterior. Aqui estamos atualizando os dados de um aluno e para isso usamos o método Update definido no Domain.

Para concluir crie a classe RemoveAlunoCommand :

using Domain.Entities;
using Domain.Interfaces;
using MediatR;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Application.CQRS.Commands
{
    public class RemoveAlunoCommand : IRequest<Aluno>
    {
        public int Id { get; set; }
        public RemoveAlunoCommand(int id)
        {
            Id = id;
        }
        public class RemoveAlunoCommandHandler : IRequestHandler<RemoveAlunoCommand, Aluno>
        {
           
private readonly IAlunoRepository _alunoRepository;
            public RemoveAlunoCommandHandler(IAlunoRepository alunoRepository)
            {
                _alunoRepository = alunoRepository ?? throw new
                    ArgumentNullException(nameof(alunoRepository));
            }

            public async Task<Aluno> Handle(RemoveAlunoCommand request,
                CancellationToken cancellationToken)
            {
                var aluno = await _alunoRepository.GetAlunoIdAsync(request.Id);

                if (aluno == null)
                {
                    throw new ApplicationException($"Entity could not be found.");
                }
                else
                {
                    var result = await _alunoRepository.RemoveAsync(aluno);
                    return result;
                }
            }
        }
    }
}

Aqui estamos removendo um aluno pelo seu Id.

1- Queries

Na pasta Queries vamos criar a classe GetAlunosQuery que vai retornar todos os alunos :

using Domain.Entities;
using Domain.Interfaces;
using MediatR;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Application.CQRS.Queries
{
    public class GetAlunosQuery : IRequest<IEnumerable<Aluno>>
    {
        public class GetAlunosQueryHandler : IRequestHandler<GetAlunosQuery, IEnumerable<Aluno>>
        {
            private readonly IAlunoRepository _alunoRepository;
            public GetAlunosQueryHandler(IAlunoRepository context)
            {
                _alunoRepository = context;
            }

            public async Task<IEnumerable<Aluno>> Handle(GetAlunosQuery query, CancellationToken cancellationToken)
            {
                var listaAlunos = await _alunoRepository.GetAlunosAsync();

                if (listaAlunos == null)
                {
                    return null;
                }
                return listaAlunos;
            }
        }
    }
}

A agora crie a classe GetAlunoIdQuery que vai retornar um aluno pelo seu Id:

using Domain.Entities;
using Domain.Interfaces;
using MediatR;
using System.Threading;
using System.Threading.Tasks;

namespace Application.CQRS.Queries
{
    public class GetAlunoIdQuery : IRequest<Aluno>
    {
        public int Id { get; set; }
        public GetAlunoIdQuery(int id)
        {
            Id = id;
        }
        public class GetAlunoIdQueryHandler : IRequestHandler<GetAlunoIdQuery, Aluno>
        {
            private readonly IAlunoRepository _alunoRepository;
            public GetAlunoIdQueryHandler(IAlunoRepository alunoRepository)
            {
                _alunoRepository = alunoRepository;
            }

            public async Task<Aluno> Handle(GetAlunoIdQuery request, CancellationToken cancellationToken)
            {
                var aluno = await _alunoRepository.GetAlunoIdAsync(request.Id);
                return aluno;
            }
        }
    }
}

Dessa forma já implementamos as consultas e os comandos seguindo o padrão CQRS e usamos o MediatR.

Agora podemos implementar a classe AlunoService na pasta Services:

using Application.CQRS.Commands;
using Application.CQRS.Queries;
using Application.DTOs;
using Application.Interfaces;
using AutoMapper;
using MediatR;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Application.Services
{
    public class AlunoService : IAlunoService
    {
      
 private readonly IMapper _mapper;
        private readonly IMediator _mediator;

        public AlunoService(IMapper mapper, IMediator mediator)
        {
            _mapper = mapper;
            _mediator = mediator;
        }

        public async Task<IEnumerable<AlunoDto>> GetAlunosAsync(CancellationToken cancellationToken = default)
        {
            var alunosQuery = new GetAlunosQuery();

            if (alunosQuery == null)
                throw new Exception($"Entity could not be loaded.");

            var result = await _mediator.Send(alunosQuery);

            return _mapper.Map<IEnumerable<AlunoDto>>(result);
        }

        public async Task<AlunoDto> GetAlunoIdAsync(int alunoId, CancellationToken cancellationToken = default)
        {
            var alunoByIdQuery = new GetAlunoIdQuery(alunoId);

            if (alunoByIdQuery == null)
                throw new Exception($"Entity could not be loaded.");

            var result = await _mediator.Send(alunoByIdQuery);

            return _mapper.Map<AlunoDto>(result);
        }

        public async Task CreateAsync(AlunoDto alunoDto, CancellationToken cancellationToken = default)
        {
            var createAlunoCommand = _mapper.Map<CreateAlunoCommand>(alunoDto);
            await _mediator.Send(createAlunoCommand);
        }

        public async Task UpdateAsync(int alunoId, AlunoDto alunoDto, CancellationToken cancellationToken = default)
        {
            var alunoUpdateCommand = _mapper.Map<UpdateAlunoCommand>(alunoDto);
            await _mediator.Send(alunoUpdateCommand);
        }

        public async Task DeleteAsync(int alunoId, CancellationToken cancellationToken = default)
        {
            var alunoRemoveCommand = new RemoveAlunoCommand(alunoId);

            if (alunoRemoveCommand == null)
                throw new Exception($"Entity could not be loaded.");

            await _mediator.Send(alunoRemoveCommand);
        }
    }
}

Aqui injetamos o AutoMapper e o MediatR no construtor da classe e usando o MediatR estamos encaminhando o Request para o respectivo comando ou consulta.

No serviço estamos usando os comandos e consultas implementadas na pasta CQRS do projeto.

Assim temos os comandos e consultas implementadas e também o serviço, e, podemos partir para a definição da camada de apresentação.

Na próxima parte do artigo iremos implementar a Web API ApiAlunos na camada Presentation.

"Ai dos que ao mal chamam bem, e ao bem mal; que fazem das trevas luz, e da luz trevas; e fazem do amargo doce, e do doce amargo!"
Isaías 5:20

Referências:


José Carlos Macoratti