Implementando CQRS com .NET 6 e MediatR
Introdução
CQRS é um padrão arquitetural com foco em separar leitura a da escrita, onde alterações de dados são realizadas pelos Commands, e leitura de dados são realizadas pelas Queries.
Quando se utiliza CQRS, temos que ter ciência de que iremos trabalhar com a consistência eventual de nossos dados, já que iremos possuir uma base de dados para leitura e outra para escrita. Eventualmente as duas bases de dados estarão dessincronizadas.
Command e Query Stack
Como podemos ver no diagrama anterior, a partir de uma interação com o usuário, é gerado um command. Esse command provoca uma alteração no estado de nossa base de dados de escrita, e então gera um event. A response e recebida pela UI, e de forma assíncrona, o nosso event é processado, atualizando os dados de nossa base de leitura. Então podemos utilizar queries para consulta de dados na base de leitura.
Teorema CAP
Consistência: É desejável que seus dados sejam consistentes. No CQRS, trabalhamos com a consistência eventual dos dados, já que nem sempre as duas bases de dados(escrita e leitura), estão atualizadas.
Disponibilidade: Um dados, por mais que ele não esteja na sua versão mais recente, ele deve ser entregue.
Tolerância a falhas: Significa que quando uma parte a sua aplicação parar de funcionar, o resto deve continuar funcionando.
Dessas opções, você só pode escolher duas, ou seja, se você quer uma aplicação com consistência, e disponibilidade, ela não vai ser tolerante a falhas, e assim segue.
Ter as 3 opções é impossível.
Quando escolhemos o CQRS, temos disponibilidade e tolerância a falhas, assim a nossa consistência é eventual.
Preparando aplicação
Primeiro vamos subir um banco SQL Server utilizando o seguinte docker-compose:
E executando o comando:
docker-compose up -d
Agora vamos criar um projeto com o template de webapi:
dotnet new sln -n Products
dotnet new webapi -n Products.Api
dotnet sln add .\Products.Api
Pronto. O primeiro passo da implementação se definir as entidades e criar contextos de escrita e leitura.
Vamos trabalhar com uma entidade produto para este exemplo.
Agora criamos nossos contextos.
Então configuramos nossas strings de conexão:
E utilizando os seguintes comandos, iremos gerar nossa estrutura de banco de dados:
dotnet ef migrations add InitialMigration --context ProductWriteContextdotnet ef migrations add InitialMigration --context ProductReadContextdotnet ef database update --context ProductWriteContext
dotnet ef database update --context ProductReadContext
Com nossa estrutura definida, vamos criar uma repositório para realizar as operações no banco de dados:
E sua configuração:
builder.Services.AddScoped<IProductRepository, ProductRepository>();
Com nossa estrutura preparada, já podemos começar a implementação referente ao CQRS.
Implementando CQRS
Vamos criar um command que irá adicionar um produto.
Primeiro criamos o modelo de dados que o command irá receber:
E o seu executor(handler):
O modelo deve implementar a interface IRequest, com o tipo de retorno do command, que é bool.
O executor deve implementar a interface IRequestHandler, com o tipo do command, que é AddProductCommand, e o tipo de retorno que é bool.
Para configurar a referência do command ao seu handler, utilizamos a seguinte linha:
builder.Services.AddMediatR(typeof(AddProductCommand).GetTypeInfo().Assembly);
E para operações de consulta, vamos criar nosso serviço de queries:
Pronto! Agora vamos criar um controller para executar nossas operações:
Agora vamos testar: