Introdução
O padrão CQRS (Command Query Responsibility Segregation) é uma abordagem de design que separa as operações de leitura e escrita em diferentes modelos. Essa separação permite otimizar o desempenho, a escalabilidade e a segurança da aplicação. O MediatR é uma biblioteca .NET que facilita a implementação desse padrão, permitindo um design mais limpo e desacoplado. Neste artigo, vamos explorar a implementação do padrão CQRS com o MediatR em uma aplicação ASP.NET Core.
Instalação do MediatR
Para começar, instale o pacote NuGet MediatR.Extensions.Microsoft.DependencyInjection
no projeto Application
. Este pacote permite integrar o MediatR com o ASP.NET Core, facilitando a injeção de dependências e o uso dos handlers.
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
Configurando o MediatR nos Controllers
O MediatR pode ser consumido diretamente nos Controllers ou definido em um Controller base herdado pelos demais. Veja abaixo a implementação em ActivitiesController
.
Exemplo de Instância em ActivitiesController
public class ActivitiesController : BaseApiController
{
private readonly IMediator _mediator;
public ActivitiesController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<ActionResult<List<Activity>>> GetActivities()
{
return await _mediator.Send(new List.Query());
}
[HttpGet("{id}")]
public async Task<ActionResult<Activity>> GetActivity(Guid id)
{
return Ok();
}
}
Implementação Utilizando um Controller Base
Para evitar a repetição de código, você pode definir um ControllerBase
que disponibiliza o MediatR para todos os controllers que o herdam.
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
namespace API.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class BaseApiController : ControllerBase
{
private IMediator _mediator;
protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService<IMediator>();
}
}
Atualizando o ActivitiesController
Com a configuração acima, o ActivitiesController
pode ser atualizado para utilizar o Mediator
disponibilizado pelo ControllerBase
.
public class ActivitiesController : BaseApiController
{
[HttpGet]
public async Task<ActionResult<List<Activity>>> GetActivities()
{
return await Mediator.Send(new List.Query());
}
[HttpGet("{id}")]
public async Task<ActionResult<Activity>> GetActivity(Guid id)
{
return await Mediator.Send(new Details.Query { Id = id });
}
}
Implementação de Handlers
Classe Details
A classe Details
é responsável por buscar uma atividade específica pelo seu ID.
namespace Application.Activities
{
public class Details
{
public class Query : IRequest<Activity>
{
public Guid Id { get; set; }
}
public class Handler : IRequestHandler<Query, Activity>
{
private readonly DataContext _context;
public Handler(DataContext context)
{
_context = context;
}
public async Task<Activity> Handle(Query request, CancellationToken cancellationToken)
{
return await _context.Activities.FindAsync(request.Id);
}
}
}
}
Atualizando o Método GetActivity
Atualize o método GetActivity
no ActivitiesController
para utilizar o handler Details
.
[HttpGet("{id}")]
public async Task<ActionResult<Activity>> GetActivity(Guid id)
{
return await Mediator.Send(new Details.Query { Id = id });
}
Classe Create
A classe Create
permite a criação de novas atividades.
namespace Application.Activities
{
public class Create
{
public class Command : IRequest
{
public Activity Activity { get; set; }
}
public class Handler : IRequestHandler<Command>
{
private readonly DataContext _context;
public Handler(DataContext context)
{
_context = context;
}
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
{
_context.Activities.Add(request.Activity);
await _context.SaveChangesAsync();
return Unit.Value;
}
}
}
}
Adicionando o Método CreateActivity
Adicione o método CreateActivity
no ActivitiesController
.
[HttpPost]
public async Task<IActionResult> CreateActivity(Activity activity)
{
return Ok(await Mediator.Send(new Create.Command { Activity = activity }));
}
Classe Edit
A classe Edit
permite a edição de atividades existentes.
namespace Application.Activities
{
public class Edit
{
public class Command : IRequest
{
public Activity Activity { get; set; }
}
public class Handler : IRequestHandler<Command>
{
private readonly DataContext _context;
public Handler(DataContext context)
{
_context = context;
}
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
{
var activity = await _context.Activities.FindAsync(request.Activity.Id);
activity.Title = request.Activity.Title ?? activity.Title;
await _context.SaveChangesAsync();
return Unit.Value;
}
}
}
}
Adicionando o Método EditActivity
Adicione o método EditActivity
no ActivitiesController
.
[HttpPut("{id}")]
public async Task<IActionResult> EditActivity(Guid id, Activity activity)
{
activity.Id = id;
return Ok(await Mediator.Send(new Edit.Command { Activity = activity }));
}
AutoMapper
O AutoMapper é uma ferramenta que facilita o mapeamento de propriedades entre objetos. Instale o pacote AutoMapper.Extensions.Microsoft.DependencyInjection
no projeto Application
.
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection
Configurando o AutoMapper
Configure o AutoMapper no método ConfigureServices
da classe Startup.cs
.
services.AddAutoMapper(typeof(MappingProfiles).Assembly);
Utilizando o AutoMapper
Utilize o AutoMapper nas classes do projeto Application.Activities
para mapear as propriedades de Activity
enviadas com o Command request
.
public class Handler : IRequestHandler<Command>
{
private readonly DataContext _context;
private readonly IMapper _mapper;
public Handler(DataContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public async Task<Unit> Handle(Command request, CancellationToken cancellationToken)
{
var activity = await _context.Activities.FindAsync(request.Activity.Id);
_mapper.Map(request.Activity, activity);
await _context.SaveChangesAsync();
return Unit.Value;
}
}
CancellationToken
O CancellationToken
é usado para cancelar uma solicitação. Envie o CancellationToken
como parâmetro para o método ToListAsync
do Entity Framework.
Considere a implementação abaixo na classe List.cs
na pasta Activities
.
public async Task<List<Activity>> Handle(Query request, CancellationToken cancellationToken)
{
try
{
for (var i = 0; i < 10; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(1000, cancellationToken);
}
}
catch (Exception ex) when (ex is TaskCancellationException)
{
_logger.LogInformation($"Erro: {ex}");
}
return await _context.Activities.ToListAsync();
}
Adicionando CancellationToken no Controller
No controller, receba o CancellationToken
e envie-o para o método que executará o processamento.
[HttpGet]
public async Task<ActionResult<List<Activity>>> GetActivities(CancellationToken ct)
{
return await Mediator.Send(new List.Query(), ct);
}
Conclusão
Implementar o padrão CQRS com o MediatR em uma aplicação ASP.NET Core proporciona vários benefícios, incluindo uma arquitetura mais limpa, desacoplada e fácil de manter. Utilizar ferramentas como AutoMapper para mapeamento automático de propriedades e CancellationToken para gerenciamento de cancelamentos de tarefas aprimora ainda mais a robustez e a escalabilidade da aplicação. Adotar boas práticas, como a separação clara de responsabilidades e o uso de injeção de dependências, contribui significativamente para a qualidade e a eficiência do desenvolvimento de software.
No responses yet