ASP .NET Core  5 - Implementando CQRS com Mediator - II


Neste artigo vamos continuar a implementação do CQRS - Command Query Responsibility Segregation usando o padrão Mediator.(Mediatr)

Continuando o artigo anterior vamos partir para realizar a implementação do CQRS usando o MediatR.

CQRS - Implementação com os comandos e as consultas

Vamos agora iniciar a implementação do  CQRS no projeto Application definindo os Commands e as Queries e vamos precisar instalar a biblioteca MediatR.

Para isso podemos usar a janela do Package Manger Console e digitar o comando:

A seguir vamos criar as seguintes pastas no projeto Application :

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

Na implementação podemos criar duas classes distintas ou podemos usar o recurso das classes internas do C# e criar as duas classes em um único arquivo. Neste exemplo eu vou criar as duas classes em um único arquivo como classes internas.

Na pasta Commands vamos definir o primeiro Command criando a classe CreateCustomerCommand que vai adicionar um novo cliente:

using DemoMediator.Domain.Entities;
using DemoMediator.Domain.Interfaces;
using MediatR;
using System.Threading;
using System.Threading.Tasks;
namespace DemoMediator.Application.Services.Commands
{
    public class CreateCustomerCommand : IRequest<int>
    {
        public string Name { get; set; }
        public string Email { get; set; }

        public class CreateProductCommandHandler : IRequestHandler<CreateCustomerCommand, int>
        {
            private readonly ICustomerRepository _context;
            private readonly IMediator _mediator;

            public CreateProductCommandHandler(ICustomerRepository context, IMediator mediator)
            {
                _context = context;
                _mediator = mediator;
            }

            public async Task<int> Handle(CreateCustomerCommand command,
                CancellationToken cancellationToken)
            {
                var customer = new Customer();
                customer.Name = command.Name;
                customer.Email = command.Email;
                _context.Add(customer);
                 return customer.Id;
            }
        }
    }
}

Vamos entender o código:

Primeiro, criamos a classe Command - CreateCustomCommand - e implementamos IRequest<int> onde estamos indicando que vamos retornar um inteiro. Esse recurso vem da biblioteca MediatR.

A classe possui duas propriedades Name e Email que representa os dados que estamos criando.

Em seguida, criamos a classe interna
CreateProductCommandHandler  que herda da classe RequestHandler<Command, int> onde estamos indicando qual Comando será tratado e o retorno esperado.

A seguir injetamos o serviço do repositório definido por _context e a instância do Mediator (_mediator) no construtor, depois implementamos o método Handle (Command request), adicionando o valor à nossa fonte de dados usando a instância do repositório.

Essa lógica vai ser seguida para implementar os outros dois comandos que iremos criar:

a- UpdateCustomerCommand - que atualiza um cliente existente

using DemoMediator.Domain.Interfaces;
using MediatR;
using System.Threading;
using System.Threading.Tasks;
namespace DemoMediator.Application.Services.Commands
{
    public class UpdateCustomerCommand : IRequest<int>
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }

        public class UpdateProductCommandHandler : IRequestHandler<UpdateCustomerCommand, int>
        {
            private readonly ICustomerRepository _context;
            private readonly IMediator _mediator;
            public UpdateProductCommandHandler(ICustomerRepository context, IMediator mediator)
            {
                _context = context;
                _mediator = mediator;
            }
            public async Task<int> Handle(UpdateCustomerCommand command, CancellationToken cancellationToken)
            {
                   var customer = await _context.GetById(command.Id);
                   if (customer == null) return default;
                    customer.Name = command.Name;
                    customer.Email = command.Email;
                   _context.Update(customer);
                    return customer.Id;
                }
            }
        }
    }
}

b- DeleteCustomerByIdCommand - que exclui um cliente existente

using DemoMediator.Domain.Interfaces;
using MediatR;
using System.Threading;
using System.Threading.Tasks;
namespace DemoMediator.Application.Services.Commands
{
    public class DeleteCustomerByIdCommand : IRequest<int>
    {
        public int Id { get; set; }

        public class DeleteProductByIdCommandHandler : IRequestHandler<DeleteCustomerByIdCommand, int>
        {
            private readonly ICustomerRepository _context;
            private readonly IMediator _mediator;
            public DeleteProductByIdCommandHandler(ICustomerRepository context, IMediator mediator)
            {
                _context = context;
                _mediator = mediator;
            }

            public async Task<int> Handle(DeleteCustomerByIdCommand command, CancellationToken cancellationToken)
            {
                var customer = await _context.GetById(command.Id);
                if (customer == null) return default;
                _context.Remove(customer);
                return customer.Id;
            }
        }
    }
}

Já criamos os Commands vamos criar a seguir as Queries.

Vamos agora criar as consultas na pasta Queries e vamos fazer isso criando classes internas.

A primeira consulta - GetAllCustomersQuery - irá retornar todos os clientes :

using DemoMediator.Domain.Entities;
using DemoMediator.Domain.Interfaces;
using MediatR;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace DemoMediator.Application.Services.Queries
{
    public class GetAllCustomersQuery : IRequest<IEnumerable<Customer>>
    {
        public class GetAllCustomersQueryHandler : IRequestHandler<GetAllCustomersQuery,
            IEnumerable<Customer>>
        {
            private readonly ICustomerRepository _context;
            private readonly IMediator _mediator;
            public GetAllCustomersQueryHandler(ICustomerRepository context, IMediator mediator)
            {
                _context = context;
                _mediator = mediator;
            }

            public async Task<IEnumerable<Customer>> Handle(GetAllCustomersQuery query,
                CancellationToken cancellationToken)
            {
                var customerList = await _context.GetAll();
                 if (customer == null) return default;
 
                 return customerList;
            }
        }
    }
}

Vamos entender o código :

Primeiro, criamos uma classe interna ou inner class chamada GetAllCustomersQuery , que implementa IRequest<IEnumerable<Customer>>. Isso significa que nosso Request vai retornar uma lista clientes.

Em seguida, criamos outra classe interna chamada
GetAllCustomersQueryHandler , que herda de IRequestHandler<GetAllCustomersQuery,IEnumerable<Customer>>. Isso significa que essa classe tratará a Query ou Consulta, neste caso, retornando a lista de clientes.

No construtor desta classe injetamos uma instância do nosso repositório e do mediator e a seguir implementamos um único método chamado Handle, que retorna os valores de nosso repositório.

A outra consulta  - GetCustomerByIdQuery - vai retornar um cliente pelo seu id, e vai seguir a mesma abordagem:

using DemoMediator.Domain.Entities;
using DemoMediator.Domain.Interfaces;
using MediatR;
using System.Threading;
using System.Threading.Tasks;
namespace DemoMediator.Application.Services.Queries
{
    public class GetCustomerByIdQuery : IRequest<Customer>
    {
        public int Id { get; set; }
        public class GetProductByIdQueryHandler : IRequestHandler<GetCustomerByIdQuery, Customer>
        {
            private readonly ICustomerRepository _context;
            private readonly IMediator _mediator;
            public GetProductByIdQueryHandler(ICustomerRepository context, IMediator mediator)
            {
                _context = context;
                _mediator = mediator;
            }

            public async Task<Customer> Handle(GetCustomerByIdQuery query, CancellationToken cancellationToken)
            {
                var customer = await _context.GetById(query.Id);
 
                if (customer == null) return default;
 
                return customer;
            }
        }
    }
}

Com isso temos os comandos e as consultas criadas usando a biblioteca MediatR e podemos agora continuar implementando o nosso controlador CustomersController para testar os comandos e consultas.

Na janela Solution Explorer veremos a seguinte estrutura e conteúdo para o projeto Application:

Na próxima parte do artigo vamos continuar implementando o controlador CustomersController.

Não se turbe o vosso coração; credes em Deus, crede também em mim.
Na casa de meu Pai há muitas moradas. Se assim não fora, eu vo-lo teria dito. Pois vou preparar-vos lugar.

João 14:1,2

Referências:


José Carlos Macoratti