CQRS - Um Paradigma Arquitetural Moderno Relacionado a I/O de Dados
- #.NET
- #C #
No último episódio: O artigo anterior, introduzimos os conceitos de Microsserviços, suas aplicações e principais características, afim de preparar o terreno para o assunto que será abordado nesse artigo. O link do material anterior pode ser acessado por esse link: DIO | Codifique o seu futuro global agora.
Vamos do começo: o que significa CQRS?
Command Query Responsibility Segregation, um padrão de arquitetura de desenvolvimento de software que resume a separar a leitura e a escrita em dois modelos: query e command, uma para leitura e outra para escrita de dados, respectivamente. O CQRS foi concebido como resposta a uma série de desafios enfrentados pelos desenvolvedores ao lidar com sistemas complexos. Entre as principais motivações para o surgimento do CQRS, destacam-se: adoção de tecnologias específicas, consistência específica para leitura e escrita, modelagem mais expressiva e escalabilidade.
- Query model: responsável pela leitura dos dados do BD e pela atualização da interface gráfica.
- Command model: responsável pela escrita de dados no BD e pela validação dos dados. Sua interação com a interface gráfica é somente receber os dados a serem escritos.
Exemplo de implementação
A arquitetura CQRS para um projeto ASP.NET MVC (Baseada no artigo do Dino Esposito)
Crie alguns projetos de biblioteca de classes, pilha de consulta e pilha de comando, e faça referência aos dois do projeto de servidor da Web principal.
A pilha de consulta
A biblioteca de classes de pilha de consulta só está preocupada com a recuperação de dados. Ela usa um modelo de dados que corresponde aos dados usados na camada de apresentação o mais próximo possível. Você raramente precisa quaisquer regras de negócios aqui, pois elas se aplicam aos comandos que alteram o estado.
O padrão de modelo de domínio popularizado pelo DDD é essencialmente uma forma de organizar a lógica do domínio. Quando você faz consultas de front-end, você está lidando apenas com parte da lógica do aplicativo e casos de uso. A lógica de negócios do termo geralmente resulta da união de lógica específica do aplicativo com a lógica invariável de domínio. Se você souber o formato de persistência e o formato de apresentação, basta mapear os dados como faria em uma consulta do bom e velho ADO.NET/SQL.
É útil se lembrar de que qualquer código que você pode chamar da camada de aplicativo representa o domínio de negócios do sistema. Portanto, é a API invariável que expressa a lógica principal do sistema. Em condições ideais, você deve garantir que nenhuma operação inconsistente e incongruente seja possível mesmo por meio da API exposta. Então para impor a natureza somente leitura da pilha de consulta, adicione uma classe wrapper em torno do objeto de contexto do Entity Framework padrão para se conectar ao banco de dados, conforme mostrado no código a seguir:
public class Database : IDisposable
{
private readonly QueryDbContext _context = new QueryDbContext();
public IQueryable<Customer> Customers
{
get { return _context.Customers; }
}
public void Dispose()
{
_context.Dispose();
}
}
A classe Matches é implementada como uma coleção DbSet na classe base DbContext. Como tal, ela fornece acesso completo ao banco de dados subjacente e você pode usá-la para configurar consultas e atualizar operações por meio de LINQ às Entidades. A etapa fundamental para configurar um pipeline de consulta é permitir o acesso ao banco de dados somente para consultas. Essa é a função da classe wrapper, onde Matches é exposta como um IQueryable. A camada de aplicativo usará a classe de wrapper do banco de dados para implementar consultas direcionadas em trazer os dados para a apresentação:
var model = new RegisterViewModel();
using (var db = new Database())
{
var list = (from m in db.Customers select m).ToList();
model.ExistingCustomers = list;
}
Agora há uma conexão direta entre a fonte de dados e a apresentação. Você está apenas lendo e formatando os dados para fins de exibição. Você espera que a autorização seja imposta na porta por meio de logons e restrições de interface do usuário. Caso contrário, você pode adicionar mais camadas ao longo do processo e habilitar a troca de dados por meio de coleções de dados IQueryable. O modelo de dados é o mesmo que o banco de dados e é de 1 para 1 com persistência. Esse modelo é às vezes chamado de árvores de expressão em camadas (LET).
Há algumas coisas que você deve observar neste momento. Primeiro, você está no pipeline de leitura em que as regras de negócios normalmente não existem. Tudo o que você pode ter aqui são filtros e regras de autorização. Essas são bem-conhecidas no nível de camada de aplicativo. Você não precisa lidar com objetos de transferência de dados ao longo do caminho. Você tem um modelo de persistência e contêineres de dados reais para o modo de exibição. No serviço de aplicativo, você acaba com o seguinte padrão:
var model = SpecificUseCaseViewModel();
model.SomeCollection = new Database()
.SomeQueryableCollection
.Where(m => SomeCondition1)
.Where(m => SomeCondition2)
.Where(m => SomeCondition3)
.Select(m => new SpecificUseCaseDto
{
// Fill up
})
.ToList();
return model;
Todos os objetos de transferência de dados especiais no trecho de código são específicos para o caso de uso de apresentação que você está implementando. Elas são apenas o que o usuário deseja ver no modo de exibição Razor, que você está criando e as classes são inevitáveis. Além disso, você pode substituir todas as cláusulas Where com métodos de extensão IQueryable ad hoc e ativar todo o código na caixa de diálogo escrita em linguagem específica do domínio.
A segunda coisa a observar sobre a pilha de consulta é relacionada a persistência. Na forma mais simples de CQRS, as pilhas de comando e consulta compartilham o mesmo banco de dados. Essa arquitetura torna o CQRS semelhante aos sistemas CRUD clássicos. Isso facilita a adotá-lo quando as pessoas são resistentes a alteração. No entanto, você pode criar o back-end para que as pilhas de comando e consulta tenham seus próprios bancos de dados otimizados para suas finalidades específicas. Sincronizar dois bancos de dados, em seguida, torna-se a outro problema.
A pilha de comando
No CQRS, a pilha de comando diz respeito apenas a execução de tarefas que modificam o estado do aplicativo. A camada de aplicativo recebe solicitações da apresentação e organiza a execução enviando comandos no pipeline. A expressão "enviar comandos ao pipeline" é a origem de vários tipos de CQRS.
No caso mais simples, enviar um comando consiste em simplesmente invocar um script de transação. Esse dispara um fluxo de trabalho simples que realiza todas as etapas exigidas pela tarefa. Enviar um comando da camada de aplicativo pode ser tão simples quanto o código a seguir:
public void Register(RegisterInputModel input)
{
// Push a command through the stack
using (var db = new CommandDbContext())
{
var c = new Customer {
FirstName = input.FirstName,
LastName = input.LastName };
db.Customers.Add(c);
db.SaveChanges();
}
}
Se necessário, você pode transferir o controle para a camada de domínio verdadeiro com serviços e o modelo de domínio onde você implementa a lógica de negócios completa. No entanto, usar o CQRS não vincula você necessariamente ao DDD e coisas como agregações, fábricas e objetos de valor. Você pode ter os benefícios da separação de comando/consulta sem a complexidade extra de um modelo de domínio.
Bibliografia:
Programas de ponta - CQRS para o aplicativo comum | Microsoft Learn
CQRS de forma clara, completa e definitiva | by Thiago Capaverde | Medium
CQRS Pattern - Azure Architecture Center | Microsoft Learn
c# - O que é CQRS e como implementar? - Stack Overflow em Português