O que é CQRS e como implementar? – c# design-pattern
Pergunta:
CQRS (Command and Query Responsibility Segregation) é uma dessas siglas que nos deparamos e não conseguimos compreender completamente seu significado e utilização.
O que é CQRS e como implementar?
Autor da pergunta rubStackOverflow
Comunidade
CQRS
Command Query Responsibility Segregation, ou CQRS, é 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.
-
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.
Hoje, aplicações modernas tem conceitos novos de disponibilidade, performance, funcionalidade e usabilidade (estamos falando de aplicações de “escritório”).
Seguinte tal estrutura, esse tipo de aplicação deve estar disponível em uma arquitetura pensada para suportar milhares de usuários, como pouquíssimos usuários (e claro, ainda nos padrões da estrutura moderna de aplicações). Querendo ou não, é um conceito de ESCALABILIDADE, o seu software/serviço seguindo o seu próprio uso.
Quando falamos da escalabilidade do software em si estamos falando
sobre ter um código e uma arquitetura que é fácil de dar manutenção,
de aumentar suas funcionalidades, de várias pessoas trabalharem nele.Eventualmente podemos usar o termo para indicar que o software pode
ser usado por uma grande quantidade de usuários (clientes). No sentido
de pessoas ou empresas
Fonte
Num exemplo simples: pensemos num saque no caixa eletrônico. Milhares de pessoas (e agora você) acessando o mesmo banco de dados, gravando e lendo dados a todo o tempo. Ignorando as filas, imagine se demorasse horas para fazer um único saque?! Um caos.
A respeito do CQRS, outra definição importante é: nada é o que parece. É, exatamente. Se você efetua uma consulta no banco de dados (query model) e por algum motivo esses dados forem alterados (command model), o que você vê não é necessariamente o dado atualizado. Trabalhar com esses dados sempre atualizados pode causar uma enorme perda de performance (lembrando, sua aplicação é moderna!).
A proposta de dividir as tarefas pode parecer um pouco mais interessante agora. Mas não, não é só isso, a segregação dos modelos diz respeito a separação física e/ou lógica dos dados.
Numa diagramação mais simples possível do CQRS, temos:
Implementação
Não existe uma única maneira de implementar o CQRS na sua aplicação,
pode ser feito de uma forma simples ou muito complexa, depende da
necessidade. Independente de como for implementado o CQRS sempre
acarreta numa complexidade extra e por isso é necessário avaliar os
cenários em que realmente são necessários trabalhar com este padrão.
Fonte
Exemplo de implementação
Daqui para baixo, um padrão CQRS simples. Já adianto, retirado daqui.
A arquitetura CQRS para um projeto ASP.NET MVC
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.
Mais links sobre implementação
De acordo com o autor do padrão ele consiste apenas em separar as operações de leitura das operações de escrita.
When most people talk about CQRS they are really speaking about
applying the CQRS pattern to the object that represents the service
boundary of the application. Consider the following pseudo-code
service definition.CustomerService
void MakeCustomerPreferred(CustomerId)
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)Applying CQRS on this would result in two services
CustomerWriteService
void MakeCustomerPreferred(CustomerId)
void ChangeCustomerLocale(CustomerId, NewLocale)
void CreateCustomer(Customer)
void EditCustomerDetails(CustomerDetails)CustomerReadService
Customer GetCustomer(CustomerId)
CustomerSet GetCustomersWithName(Name)
CustomerSet GetPreferredCustomers()That is it. That is the entirety of the CQRS pattern. There is nothing
more to it than that…
Tradução
Quando a maior parte das pessoas falam sobre CQRS elas realmente estão
falando em como aplicar o padrão CQRS a um objeto que representa a
camada de serviço duma aplicação. Considere a seguinte definição de
serviço.…
Aplicar o padrão CQRS resultaria em dois serviços
….
É só isto mesmo. O padrao CQRS é sómente isso. Não há mais nada nele para
além disso…
Fonte – O próprio criador do padrão escreveu este texto.
Como a questão contem a tag C#, o material mais oficial que encontrei é:
- Command and Query Responsibility Segregation (CQRS) Pattern, contendo exemplos de como usar CQRS com C#
- CQRS Journey, referências sobre CQRS
A princípio CQRS é ter duas APIs, uma para fazer consultas, e outra para comandar o sistema.
Acidentalmente (pesquisando sobre Groovy), descobri que isso está na moda:
Many microservices implementations will move away from their
REST/JSON beginnings and truly embrace event-driven architectures,
with the hardest problems being solved by Event Sourcing and CQRS
implementations.
Tradução:
Muitas implementações de microserviços se afastarão de seus primórdios
REST/JSON e verdadeiramente abraçarão arquiteturas orientadas a
eventos, com os problemas mais difíceis sendo resolvidos por
implementações de Event Sourcing e CQRS.
Qual a relevância da minha observação?
Resposta: Event Sourcing
O exemplo em C# sobre CQRS que você encontra ali no link do MSDN, parece bem orientado a Event Sourcing. Normalmente, elas são implementadas em conjunto, CQRS e Event Sourcing.
The CQRS pattern is often used in conjunction with the Event Sourcing pattern.
A razão disso não é pertinente neste momento (se por acaso estiver curioso, faça outra pergunta no StackOverflow). O problema é que Event Sourcing complica bastante a implementação do CQRS.
A própria documentação fala sobre disso:
The inherent complexity of the CQRS pattern when used in conjunction
with Event Sourcing can make a successful implementation more
difficult
Agora, você quer saber como implementar CQRS, com ou sem Event Sourcing?






