ASP .NET Core - CQRS e Mediator - I
Hoje vamos fazer uma breve revisão do CQRS - Command Query Responsibility Segregation e mostrar como a biblioteca MediatR pode nos ajudar quando tratamos com o CQRS. |
Eu já apresentei o CQRS neste artigo - ASP .NET Core 3.1 - Entendendo o CQRS e também já abordei o padrão Mediator neste artigo: O padrão de projeto Mediator.
CQRS e o padrão Mediator
A biblioteca MediatR foi criada para facilitar a implementação de dois padrões de projeto : CQRS e Mediator.
Vejamos um breve resumo de cada um destes padrões.
O padrão CQRS
CQRS significa "Command Query
Responsibility Segregation” ou numa tradução livre “Segregação
de Responsabilidade de Consulta de Comando”. Como o acrônimo sugere,
trata-se de dividir a responsabilidade de comandos (escrita) e consultas
(leituras) em diferentes modelos.
Se pensarmos no padrão CRUD (Create-Read-Update-Delete)
que é muito usado normalmente temos a interface do usuário interagindo
com um armazenamento de dados responsável por todas as quatro operações.
Em vez disso, o CQRS nos faria dividir essas operações em dois modelos, um para as consultas (O Read ou a letra "R" do CRUD) e outro para os comandos (o “CUD”).
Usando o
CQRS o aplicativo separa os modelos de consulta e
comando. Dessa forma o padrão CQRS não faz requisitos formais de como essa
separação ocorre e a implementação pode ser tão simples quanto uma classe
separada no mesmo aplicativo e pode ser usado até aplicativos físicos separados
em servidores diferentes. Essa decisão seria baseada em fatores como requisitos
de dimensionamento e infraestrutura.
O ponto chave é que, para criar um sistema com o padrão CQRS, precisamos apenas
dividir as leituras das gravações.
Simples assim...
Qual é o problema que o CQRS se propõe a resolver ?
Para aplicações com uma grande quantidade de usuários acessando uma ou mais base de dados com uma grande quantidade de informações as premissas de desempenho no acesso aos dados, a escalabilidade da aplicação e a sua disponibilidade acabam sendo de suma importância para o sucesso do projeto.
Uma aplicação de grande porte que faz uso intenso de acesso a dados pode enfrentar problemas de lentidão, deadlocks e timouts afetando a escalabilidade, a disponibilidade e o desempenho. Este cenário seria o pior possível para qualquer tipo de aplicação.
Esses problemas podem estar ocorrendo devido ao tempo gasto na consulta e na persistência das informações principalmente quando não temos uma divisão clara dessas responsabilidade e de como elas podem estar afetando a aplicação. Afinal seria melhor dar prioridade para a leitura de dos dados ou priorizar a gravação dos dados como uma forma alavancar a aplicação ?
O CQRS nos permite “nos libertar” dessas considerações e dar a cada abordagem, leitura e/ou gravação, o design e a consideração iguais que elas merecem, sem nos preocupar com o impacto que elas podem causar, e, isso traz enormes benefícios no desempenho e na agilidade, especialmente se houver equipes separadas trabalhando nessas abordagens.
Dessa forma o CQRS nos orienta a dividir a responsabilidade de gravação e escrita onde podemos ter meios distintos para realizar a gravação e realizar as consultas e também podemos fazer isso em banco de dados diferentes.
O padrão Mediator
O padrão Mediator lida com o desacoplamento, colocando uma camada intermediária entre os componentes, chamada de "Mediador", que será sua única fonte de dependências.
Assim este padrão define um objeto que encapsula como os objetos interagem uns com os outros. Em vez de dois ou mais objetos dependerem diretamente um do outro, eles interagem com um "mediador", que é responsável por enviar essas interações para a outra parte:
Os componentes delegam suas chamadas de função ao Mediador e este decide qual componente precisa ser chamado e para quê. Isso garante que os componentes fiquem "fracamente acoplados" uns aos outros e não chamem uns aos outros diretamente. Isso também cria a oportunidade de um componente ser substituído por outra implementação, se necessário, e o sistema não será afetado.
Como a biblioteca MediatR ajuda na
implementação do CQRS e do Mediator
Podemos pensar na biblioteca MediatR como uma
implementação do Mediator que nos ajuda a construir
sistemas usando CQRS. Toda a comunicação entre a interface do usuário e o
armazenamento de dados ocorre via MediatR.
Como no CQRS, as responsabilidades de
Consulta e Comandos são assumidas por seus
respectivos Handlers e torna-se muito difícil fazer
a conexão manualmente entre um Comando com seu Handler
e manter os mapeamentos.
É aqui que o padrão Mediator entra em cena: ele encapsula essas interações e cuida do mapeamento para nós.
E na plataforma .NET podemos usar a biblioteca MediatR que traz a implementação do Mediator que usamos na implementação do CQRS.
CQRS - Implementação com o padrão Mediator usando o MediatR
Vamos configurar um projeto ASP .NET Core do tipo Web API bem simples para mostrar a utilização da biblioteca MediatR.
Nota: Como nosso foco será mostrar o uso do MediatR com CQRS eu não vou me preocupar com a arquitetura da aplicação visando a separação das responsabilidades segundo o padrão da clean architecture. Em outro artigo eu vou mostrar como fazer essa implementação usando a Clean Architecture.
recursos usados:
Criando o projeto API no VS 2019
Abra o VS 2019 Community e crie um novo projeto via menu File-> New Project;
Selecione o template ASP .NET Core Web Application, e, Informe o nome da solução DemoCQRS (ou outro nome a seu gosto).
A seguir selecione .NET Core e ASP .NET Core 5.0 e marque o template ASP .NET Core Web API e as configurações conforme figura abaixo:
Depois que o projeto foi criado, precisamos adicionar a referência aos seguintes pacotes:
Nota: Para instalar use o comando: Install-Package <nome> --version X.X.X
Vamos aproveitar e já configurar o serviço do MediatR no projeto API incluindo no método ConfigureServices da classe Startup o código destacado em azul :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "DemoMediator.API", Version = "v1" });
});
}
|
Para concluir vamos remover do projeto controlador WeatherForecastController.cs e a classe WeatherForecast.cs.
Criando o controlador TestesController
Vamos criar um novo controlador na pasta Controllers chamado TestesController com o seguinte código:
using MediatR;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace DemoCQRS.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TestesController : ControllerBase
{
private readonly IMediator _mediator;
public TestesController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "cqrs", "mediator" };
}
[HttpGet("{id}")]
public string Get(int id)
{
return "mediatr";
}
[HttpPost]
public void Post([FromBody] string value)
{
}
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
|
Neste código criamos um controlador com os métodos GET, POST, PUT e DELETE e no construtor do controlador injetamos uma instância do MediatR.
Para tornar o artigo mais simples e focar no CQRS e no MediatR não vamos usar um banco de dados relacional. Vamos criar uma classe que encapsula esta responsabilidade.
Para isso vamos criar uma pasta Data no projeto e nesta pasta vamos criar uma classe FakeData.
using System.Collections.Generic;
namespace DemoCQRS.Data
{
public class FakeData
{
private static List<string> _values;
public FakeData()
{
_values = new List<string>
{
"cqrs",
"mediatr",
"mediator"
};
}
public void AddValue(string value)
{
_values.Add(value);
}
public IEnumerable<string> GetAllValues()
{
return _values;
}
}
}
|
A seguir vamos registrar o serviço para configurar o nosso FakeData como um Singleton incluindo o código a seguir no método ConfigureServices da classe Startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<FakeData>();
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "DemoCQRS", Version = "v1" });
});
}
|
Iniciando com o CQRS - Separando os comandos e as consultas
Já vimos que o padrão CQRS divide as leituras das gravações e vamos fazer isso agora.
Para organizar melhor o projeto vamos criar uma pasta Services e dentro desta pasta vamos criar duas pastas no projeto :
Usaremos essas pastas para separar nossos modelos de leitura e escrita. Lembrando que essa abordagem foi feita de forma bem simples para facilitar o entendimento do assunto principal deste artigo.
Fazendo requests com o MediatR
Os requests MediatR são mensagens no estilo Request/Response muito simples, em que um único Request é tratado por um único Handler ou manipulador de maneira síncrona.
Existem dois tipos de Request no MediatR :
Usaremos a nossa
fonte de dados FakeData que criamos
anteriormente para implementar alguns Requests
MediatR.
Primeiro, vamos criar uma solicitação que retorna todos os valores de nosso
FakeData.
Vamos criar na pasta Services/Queries a classe GetValuesQuery conforme o código a seguir:
using DemoCQRS.Data;
using MediatR;
using System.Collections.Generic;
namespace DemoCQRS.Services.Queries
{
public class GetValuesQuery
{
public class Query : IRequest<IEnumerable<string>> { }
public class Handler : RequestHandler<Query, IEnumerable<string>>
{
private readonly FakeData _db;
public Handler(FakeData db)
{
_db = db;
}
protected override IEnumerable<string> Handle(Query request)
{
return _db.GetAllValues();
}
}
}
}
|
Vamos entender o código :
Primeiro, criamos
uma classe interna ou inner class chamada Query,
que implementa IRequest<IEnumerable<string>>. Isso
significa que nosso Request vai retornar uma
lista de strings.
Em seguida, criamos outra classe interna chamada
Handler, que herda de
RequestHandler<Query,IEnumerable<string>>. Isso significa que essa classe
tratará a Query ou Consulta, neste caso, retornando a lista de
strings.
No construtor desta classe injetamos uma instância do nosso serviço de dados FakeData e a seguir implementamos um único método chamado Handle, que retorna os valores de nosso FakeData.
Observe que
colocamos a consulta e o manipulador(handler) na mesma classe usando o
recurso das classes internas.
Mas, podemos colocá-los em classes ou projetos separados, se preferirmos. No
entanto, colocando nas mesmas classes fica mais fácil a localização e a
manutenção do código.
Estamos usando uma abordagem síncrona para simplificar e devido à forma como
estamos implementando uma lista de valores na memória. No entanto, a biblioteca
MediatR suporta o assincronismo com async/await.
Testando o Request MediatR
Para chamar nosso Request precisamos apenas modificar o método Get() em nosso TestesController:
using MediatR;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace DemoCQRS.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TestesController : ControllerBase
{
private readonly IMediator _mediator;
public TestesController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async System.Threading.Tasks.Task<IEnumerable<string>> GetAsync()
{
return await _mediator.Send(new Services.Queries.GetValuesQuery.Query());
}
...
}
|
Veja como é simples enviar um Request ao MediatR.
Note que não
estamos considerando uma dependência de FakeData e nem temos alguma ideia
de como a consulta é tratada. Este é um dos princípios do padrão Mediator.
Agora vamos verificar se tudo está funcionando conforme o esperado.
Primeiro, vamos pressionar CTRL + F5 para construir
e executar nosso aplicativo, e devemos a interface do
Swagger exibindo os endpoints definidos em nosso controlador
TestesController:
Vamos acionar o endpoint GET /api/Testes que invocar o método GetValuesQuery
Obtemos os valores
conforme o esperado o que prova que o MediatR está
funcionando corretamente, pois os valores que vemos são aqueles inicializados
pelo nosso FakeData. Acabamos de implementar nossa
primeira "Consulta" no CQRS.
Na próxima parte do artigo veremos o outro tipo de Request
MediatR, que não retorna um valor, ou seja, um “Command” ou Comando.
"Os que confiam no SENHOR serão como
o monte de Sião, que não se abala, mas permanece para sempre."
Salmos 125:1
Referências:
ASP .NET Core - Implementando a segurança com
ASP.NET Core MVC - Criando um Dashboard .
C# - Gerando QRCode - Macoratti
ASP .NET - Gerando QRCode com a API do Google
ASP .NET Core 3.1 - Entendendo CQRS
ASP .NET Core - Usando CQRS com MediatR
ASP .NET Core 5 - Implementando CQRS usando Mediator
ASP .NET Core - Implementando o Mediator com MediatR