Implementando CQS com MediatR e ASP.NET Core

Nicolas Takashi
6 min readJul 7, 2019

No último artigo eu falei sobre a parte introdutória e teórica do padrão CQS (Command Query Separation), se você ainda não viu aconselho você dar uma pause na leitura e ir até o artigo anterior para entender melhor o que vamos fazer por aqui.

Sem mais delongas vamos ao que interessa, vamos construir uma aplicação de demonstração para implementar os conceitos que falamos anteriormente.

Projeto ASP.NET Core Web API

Primeira coisa que vamos precisar é um projeto ASP.NET Core Web API, vou partir do princípio que você já sabe como criar esse projeto, após criar um novo projeto você deve ter algo parecido com a imagem abaixo.

ASP.NET Core Web API Project

MediatR Instalando e configurando

Para deixar as coisas um pouco mais interessante, vamos adicionar um pacote nuget chamado MediatR.

O MediatR é um projeto criado pelo Jimmy Boggard que em sua essência é uma implementação do mediator.

E nós vamos utiliza-lo como um Bus em memória para fazer o dispatch das nossas commands e queries.

Para adicionar o MediatR dentro do nosso projeto basta adicionar os pacotes.

Install-Package MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection

Feito isso nós iremos realizar uma mudança em nosso Startup que deverá ser igual a que temos na linha 24.

Dentro do MediatR temos dois tipos de mensagens que vamos ver abaixo, porém vamos utilizar apenas um desses dois tipos nesse primeiro momento.

Request e Response

Esse tipo de mensagem é disparado para um único Handler e será o tipo que utilizaremos para disparar nossos objetos de Command e Query

Notification

Esse tipo de mensagem é disparado para mais de um Handler ideal para quando queremos disparar eventos que possam afetar mais de um interessado.

Criando uma Command e CommandHandler

Voltando ao que nós gostamos, agora vamos realizar a implementação de um objeto do tipo Command para podermos causar nosso primeiro efeito colateral em nossa aplicação.

O código abaixo temos uma command chamada CreateUserCommand eu particularmente gosto de dar nomes que demonstre a intenção da command.

Como podemos ver não temos grande complexidade dentro desse objeto, apenas duas propriedades e a implementação de uma interface chamada IRequest.

Durante o processo de estudo de CQS me deparei com diversas implementações algumas eu gostei outras não.

Eu irei realizar uma implementação nesse artigo onde teremos um Command Handler por tipo de entidade por exemplo: Nós estamos trabalhando com a entidade User com isso criei um Command Handler chamado UserCommandHandler, caso tivessemos uma outra entidade e precisasse realizar qualquer tipo de efeito colateral nela teria um Command Handler para atender as suas necessidades.

Se repararmos o que nós temos acima é uma classe que implementa uma outra classe chamada AsyncRequestHandler que é fornecida pelo MediatR para trabalharmos com mensagens do tipo IRequest de forma assíncrona.

O MediatR será o responsável por invocar o método Handle de acordo com o tipo que lhe for chamado, veremos como isso será feito mais para frente. Não dei ênfase no que está no corpo do método Handle, pois não faz parte do conceito é apenas um trecho de código para ser exemplificado.

Agora vamos precisar registrar o nosso Command Handler em nosso container de Injeção de Dependência, eu estou utilizando o container padrão do ASP.NET Core porém o MediatR tem suporte a outros containers como o Autofac.

No código acima temos o registro de um mapeamento na linha 26, onde falamos que a nossa Command vai ser manipulada pelo nosso CommandHanler.

Após essa configuração já ensinamos para o MediatR como ele vai resolver o Handler apropriado de acordo com a Command que enviarmos, agora vamos para o nosso controler chamado UserController onde realizaremos o disparo de uma Command para criar um novo usuário.

Acredito que não tenha novidade no código acima, temos um controller que possui uma Action chamada CreateUserAsync onde ele recebe um objeto JSON que seria a nossa command.

Esse controller tem a dependência do MediatR que nos fornece uma interface chamada IMediator com dois métodos Send e Push, como estamos trabalhando apenas com o tipo de mensagem IRequest vamos utilizar por horas apenas o método Send como podemos ver na linha 21.

A partir desse momento passamos a responsabilidade para o MediatR descobrir qual Handler deve ser invocado para trabalhar com a Command fornecida.

Criando uma Query Result, Query e QueryHandler

O Processo para criar uma Query não é muito diferente do que criar uma Command, basicamente o que vai mudar em primeiro momento é o tipo de objeto que vamos implementar em nossa Query.

Query Result

Antes de criarmos nosso objeto principal de Query, precisamos criar o nosso objeto de resultado, ou seja o objeto que será a resposta da nossa query, assim como a nossa classe abaixo:

Repare que o objeto acima é uma classe POCO sem nenhum tipo de inteligência, ela serve apenas como uma sacola de dados para levar o resultado de um objeto para quem solicitou.

Query

Agora que já temos o nosso objeto de retorno, vamos iniciar a criação do nosso objeto de entrada para buscar registros referente a nossa entidade, assim como o código abaixo:

Vamos as diferenças desse objeto que representa a nossa Query para o objeto que representa a nossa Command.

Primeiro ponto de diferença é a interface que implementamos, em sua essência ela continua ser o contrato IRequest do MediatR, porém estamos utilizando ele de uma maneira diferente, estamos informando que é uma mensagem que terá um retorno diferente da nossa Command.

Segundo ponto, podemos notar uma pequena diferença é que não tenho um construtor default e temos um Factory Method para auxiliar a criação desse objeto em nossa action, quando mostrar o código do controller vocês entenderão o motivo.

Command Handler

Chegando ao ponto final da implementação da nossa Query, vamos para a parte do Query Handler que vai basicamente funcionar exatamente igual ao Command Handler e a única diferença será que o Handler terá um tipo de retorno, assim como vemos no código abaixo:

Repare que não temos grande complexidade, apenas acessamos o nosso repositório informando os parâmetros que estão no nosso objeto Query, e mapeamos manualmente o retorno do repositório para o nosso Query Result.

Nesse exemplo optei por realizar um mapeamento manual, já que não teríamos um objeto muito grande, porém se você preferir em um projeto real você poderia utilizar bibliotecas que ajudem com isso tal como o AutoMapper.

Registrando dependências

Assim como fizemos com a Command e Command Handler nós precisamos também registrar a nossa Query e Query Handler em nosso container de Injeção de dependência para que o MediatR consiga resolver quando realizarmos o dispatch de uma query, o resultado final da nossa classe Startup seria algo parecido com o código abaixo:

Disparando uma Query a partir do Controller

Agora que já fizemos tudo que é necessário para executarmos as nossas queries, vamos adicionar uma implementação dentro do nosso controller para poder solicitar os usuários registrados com paginação.

Note que o método que obtém os usuários de forma paginada é um HTTP GET com isso nós recebemos os valores de page e pageSize através de Query String e depois construímos o GetPagedUsersQuery utilizando o seu Factory Method.

Considerações finais

O post foi bem extenso porém tentei de alguma maneira cobrir todos os passos básicos para essa implementação do padrão CQS.

Uma coisa que é importante ressaltar é que essa não é a única implementação que você irá encontrar durante os seus estudos, e isso não quer dizer que essa seja a melhor ou a mais correta.

Um ponto que acho interessante em comentar no fim do artigo é você pode usar a literatura como um guia e moldar a implementação conforme melhor atender as suas necessidades.

Em diversos projetos eu não faço a implementação dos objetos de Query como fiz aqui no artigo, já teve implementações onde acessava um repositório apenas de leitura direto no Controller.

SIM eu fiz isso e sei que tem gente que é extremamente contra esse tipo de abordagem, porém é como disse ali acima, entenda o padrão e veja como ele pode melhor atender as suas necessidades.

Conclusão

Espero que vocês tenham gostado do artigo e caso exista qualquer dúvida ou sugestão eu ficarei bem feliz em ouvir.

Todo o código fonte que utilizamos para realizar demonstração estará disponível em meu Github.

--

--

Nicolas Takashi

I love to speak, teach, and write about distributed systems, cloud computing, architecture, systems engineering, and APIs.