Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
CQRS (Command Query Responsibility Segregation) é um padrão de design que segrega operações de leitura e gravação para um armazenamento de dados em modelos de dados separados. Essa abordagem permite que cada modelo seja otimizado de forma independente e pode melhorar o desempenho, a escalabilidade e a segurança de um aplicativo.
Contexto e problema
Em uma arquitetura tradicional, um único modelo de dados é frequentemente usado para operações de leitura e gravação. Essa abordagem é simples e adequada para operações básicas de criação, leitura, atualização e exclusão (CRUD).
À medida que os aplicativos crescem, pode se tornar cada vez mais difícil otimizar as operações de leitura e gravação em um único modelo de dados. As operações de leitura e gravação geralmente têm requisitos de desempenho e dimensionamento diferentes. Uma arquitetura CRUD tradicional não leva em conta essa assimetria, o que pode resultar nos seguintes desafios:
Incompatibilidade de dados: As representações de leitura e gravação de dados geralmente diferem. Alguns campos que são necessários durante as atualizações podem ser desnecessários durante as operações de leitura.
Contenção de bloqueio: Operações paralelas no mesmo conjunto de dados podem causar contenção de bloqueio.
Problemas de desempenho: A abordagem tradicional pode ter um efeito negativo no desempenho devido à carga no armazenamento de dados e na camada de acesso a dados e à complexidade das consultas necessárias para recuperar informações.
Desafios de segurança: Pode ser difícil gerenciar a segurança quando as entidades estão sujeitas a operações de leitura e gravação. Essa sobreposição pode expor dados em contextos não intencionais.
A combinação destas responsabilidades pode resultar num modelo demasiado complicado.
Solução
Use o padrão CQRS para separar operações de gravação, ou comandos, de operações de leitura ou consultas. Os comandos atualizam dados. As consultas recuperam dados. O padrão CQRS é útil em cenários que exigem uma separação clara entre comandos e leituras.
Entenda os comandos. Os comandos devem representar tarefas comerciais específicas em vez de atualizações de dados de baixo nível. Por exemplo, em um aplicativo de reserva de hotel, use o comando "Reservar quarto de hotel" em vez de "Definir ReservationStatus como Reservado". Essa abordagem captura melhor a intenção do usuário e alinha os comandos com os processos de negócios. Para ajudar a garantir que os comandos sejam bem-sucedidos, talvez seja necessário refinar o fluxo de interação do usuário e a lógica do lado do servidor e considerar o processamento assíncrono.
Área de aperfeiçoamento Recomendação Validação do lado do cliente Valide condições específicas antes de enviar o comando para evitar falhas óbvias. Por exemplo, se não houver quartos disponíveis, desative o botão "Reservar" e forneça uma mensagem clara e fácil de usar na interface do usuário que explique por que a reserva não é possível. Essa configuração reduz solicitações desnecessárias do servidor e fornece feedback imediato aos usuários, o que melhora sua experiência. Lógica do lado do servidor Aprimore a lógica de negócios para lidar com casos extremos e falhas de forma eficiente. Por exemplo, para lidar com condições de concorrência, como vários utilizadores tentando reservar o último quarto disponível, considere adicionar utilizadores a uma lista de espera ou sugerir alternativas. Processamento assíncrono Processe comandos de forma assíncrona , colocando-os em uma fila, em vez de manipulá-los de forma síncrona. Compreender interrogações. As consultas nunca alteram os dados. Em vez disso, eles retornam objetos de transferência de dados (DTOs) que apresentam os dados necessários em um formato conveniente, sem qualquer lógica de domínio. Esta separação distinta de responsabilidades simplifica a conceção e implementação do sistema.
Separe modelos de leitura e modelos de escrita
Separar o modelo de leitura do modelo de gravação simplifica o design e a implementação do sistema, abordando preocupações específicas para gravações e leituras de dados. Essa separação melhora a clareza, a escalabilidade e o desempenho, mas introduz compensações. Por exemplo, ferramentas de andaime, como estruturas de mapeamento objeto-relacional (O/RM), não podem gerar automaticamente código CQRS a partir de um esquema de banco de dados, portanto, você precisa de lógica personalizada para preencher a lacuna.
As seções a seguir descrevem duas abordagens principais para implementar a separação de modelo de leitura e gravação no CQRS. Cada abordagem tem benefícios e desafios exclusivos, como sincronização e gerenciamento de consistência.
Separe modelos em um único armazenamento de dados
Essa abordagem representa o nível fundamental do CQRS, onde os modelos de leitura e gravação compartilham um único banco de dados subjacente, mas mantêm lógicas distintas para suas operações. Uma arquitetura CQRS básica permite que você delineie o modelo de gravação a partir do modelo de leitura enquanto confia em um armazenamento de dados compartilhado.
Essa abordagem melhora a clareza, o desempenho e a escalabilidade, definindo modelos distintos para lidar com questões de leitura e gravação.
Um modelo de gravação é projetado para lidar com comandos que atualizam ou persistem dados. Ele inclui validação e lógica de domínio e ajuda a garantir a consistência dos dados, otimizando a integridade transacional e os processos de negócios.
Um modelo de leitura é projetado para servir consultas para recuperar dados. Ele se concentra na geração de DTOs ou projeções que são otimizadas para a camada de apresentação. Ele melhora o desempenho e a capacidade de resposta da consulta, evitando a lógica do domínio.
Separe modelos em diferentes armazenamentos de dados
Uma implementação CQRS mais avançada usa armazenamentos de dados distintos para os modelos de leitura e gravação. A separação dos armazenamentos de dados de leitura e gravação permite dimensionar cada modelo para corresponder à carga. Ele também permite que você use uma tecnologia de armazenamento diferente para cada armazenamento de dados. Você pode usar um banco de dados de documentos para o armazenamento de dados de leitura e um banco de dados relacional para o armazenamento de dados de gravação.
Ao usar armazenamentos de dados separados, você deve garantir que ambos permaneçam sincronizados. Um padrão comum é fazer com que o modelo de gravação publique eventos quando atualiza o banco de dados, que o modelo de leitura usa para atualizar seus dados. Para obter mais informações sobre como usar eventos, consulte Estilo de arquitetura orientada a eventos. Como geralmente não é possível inscrever agentes de mensagens e bancos de dados em uma única transação distribuída, podem ocorrer desafios de consistência quando você atualiza o banco de dados e a publicação de eventos. Para obter mais informações, consulte Idempotent message processing.
O armazenamento de dados de leitura pode usar o seu próprio esquema de dados, otimizado para consultas. Por exemplo, ele pode armazenar uma vista materializada dos dados para evitar junções complexas ou mapeamentos O/RM. O armazenamento de dados de leitura pode ser uma réplica somente leitura do armazenamento de gravação ou ter uma estrutura diferente. Multiplicar réplicas de leitura somente pode melhorar o desempenho, reduzindo a latência e aumentando a disponibilidade, especialmente em cenários distribuídos.
Benefícios do CQRS
Escalonamento independente. O CQRS permite que os modelos de leitura e gravação sejam dimensionados de forma independente. Essa abordagem pode ajudar a minimizar a contenção de bloqueio e melhorar o desempenho do sistema sob carga.
Esquemas de dados otimizados. As operações de leitura podem usar um esquema otimizado para consultas. As operações de gravação usam um esquema otimizado para atualizações.
Segurança. Ao separar leituras e gravações, você pode garantir que apenas as entidades ou operações de domínio apropriadas tenham permissão para executar ações de gravação nos dados.
Separação de preocupações. Separar as responsabilidades de leitura e escrita resulta em modelos mais limpos e fáceis de manter. O lado de gravação normalmente processa uma lógica de negócios complexa. O lado da leitura pode permanecer simples e focado na eficiência nas consultas.
Consultas mais simples. Quando você armazena uma exibição materializada no banco de dados de leitura, o aplicativo pode evitar junções complexas quando consulta.
Problemas e considerações
Considere os seguintes pontos ao decidir como implementar esse padrão:
Aumento da complexidade. O conceito central do CQRS é simples, mas pode introduzir uma complexidade significativa no design do aplicativo, especificamente quando combinado com o padrão Event Sourcing.
Desafios de mensagens. As mensagens não são um requisito para o CQRS, mas você costuma usá-las para processar comandos e publicar eventos de atualização. Quando as mensagens são incluídas, o sistema deve levar em conta possíveis problemas, como falhas de mensagens, duplicatas e tentativas. Para obter mais informações sobre estratégias para lidar com comandos com prioridades variáveis, consulte Filas de prioridade.
Eventual coerência. Quando os bancos de dados de leitura e de gravação são separados, os dados de leitura podem não mostrar as alterações mais recentes imediatamente. Este atraso resulta em dados obsoletos. Garantir que o repositório de modelos de leitura permaneça up-toatualizado com as alterações no repositório de modelos de gravação pode ser um desafio. Além disso, detetar e lidar com cenários em que um usuário age em dados obsoletos requer uma consideração cuidadosa.
Quando usar este padrão
Utilize este padrão quando:
Você trabalha em ambientes colaborativos. Em ambientes onde vários usuários acessam e modificam os mesmos dados simultaneamente, o CQRS ajuda a reduzir os conflitos de mesclagem. Os comandos podem incluir granularidade suficiente para evitar conflitos, e o sistema pode resolver quaisquer conflitos que ocorram dentro da lógica de comando.
Você tem interfaces de usuário baseadas em tarefas. Aplicativos que guiam os usuários através de processos complexos como uma série de etapas ou com modelos de domínio complexos se beneficiam do CQRS.
O modelo de gravação tem uma pilha completa de processamento de comandos com lógica empresarial, validação de dados de entrada e validação empresarial. O modelo de escrita pode tratar um conjunto de objetos associados como uma única unidade para alterações de dados, o que é conhecido como um agregado na terminologia de design orientado por domínio. O modelo de gravação também pode ajudar a garantir que esses objetos estejam sempre em um estado consistente.
O modelo de leitura não tem lógica de negócios ou pilha de validação. Ele retorna um DTO para uso em um modelo de exibição. O modelo de leitura é eventualmente consistente com o modelo de escrita.
Você precisa de ajuste de desempenho. Os sistemas em que o desempenho das leituras de dados deve ser ajustado separadamente do desempenho das gravações de dados se beneficiam do CQRS. Esse padrão é especialmente benéfico quando o número de leituras é maior do que o número de gravações. O modelo de leitura é dimensionado horizontalmente para lidar com grandes volumes de consulta. O modelo de escrita opera em um número menor de instâncias para minimizar conflitos de fusão e manter a consistência.
Separação das preocupações de desenvolvimento. O CQRS permite que as equipas trabalhem de forma independente. Uma equipe implementa a lógica de negócios complexa no modelo de gravação e outra equipe desenvolve o modelo de leitura e os componentes da interface do usuário.
Você tem sistemas em evolução. O CQRS suporta sistemas que evoluem ao longo do tempo. Ele acomoda novas versões de modelos, alterações frequentes nas regras de negócios ou outras modificações sem afetar a funcionalidade existente.
Você precisa de integração de sistemas: Os sistemas que se integram com outros subsistemas, especialmente sistemas que usam o padrão Event Sourcing, permanecem disponíveis mesmo se um subsistema falhar temporariamente. O CQRS isola falhas, o que impede que um único componente afete todo o sistema.
Este padrão pode não ser adequado quando:
O domínio ou as regras de negócio são simples.
Uma interface de usuário simples no estilo CRUD e operações de acesso a dados são suficientes.
Design da carga de trabalho
Avalie como usar o padrão CQRS no design de uma carga de trabalho para abordar as metas e os princípios abordados nos pilares do Azure Well-Architected Framework. A tabela a seguir fornece orientação sobre como esse padrão suporta as metas do pilar Eficiência de desempenho.
Pilar | Como esse padrão suporta os objetivos do pilar |
---|---|
A Eficiência de Desempenho ajuda sua carga de trabalho a atender às demandas de forma eficiente por meio de otimizações em escala, dados e código. | A separação de operações de leitura e gravação em altas cargas de trabalho de leitura para gravação permite otimizações direcionadas de desempenho e dimensionamento para a finalidade específica de cada operação. - PE:05 Dimensionamento e particionamento - PE:08 Desempenho dos dados |
Considere quaisquer compensações em relação aos objetivos dos outros pilares que esse padrão possa introduzir.
Combine os padrões Event Sourcing e CQRS
Algumas implementações do CQRS incorporam o padrão Event Sourcing. Este padrão armazena o estado do sistema como uma série cronológica de eventos. Cada evento captura as alterações feitas nos dados em um momento específico. Para determinar o estado atual, o sistema repete esses eventos em ordem. Nesta configuração:
O repositório de eventos é o modelo de escrita e a fonte única de verdade.
O modelo de leitura gera visões materializadas desses eventos, normalmente de forma altamente desnormalizada. Essas exibições otimizam a recuperação de dados adaptando estruturas para requisitos de consulta e exibição.
Benefícios da combinação dos padrões Event Sourcing e CQRS
Os mesmos eventos que atualizam o modelo de gravação podem servir como entradas para o modelo de leitura. O modelo de leitura pode então criar um instantâneo em tempo real do estado atual. Esses instantâneos otimizam as consultas fornecendo visualizações eficientes e pré-computadas dos dados.
Em vez de armazenar diretamente o estado atual, o sistema usa um fluxo de eventos como armazenamento de escrita. Essa abordagem reduz os conflitos de atualização em agregações e melhora o desempenho e a escalabilidade. O sistema pode processar esses eventos de forma assíncrona para criar ou atualizar exibições materializadas para o armazenamento de dados de leitura.
Como o repositório de eventos atua como a única fonte de verdade, você pode facilmente regenerar visualizações materializadas ou adaptar-se a mudanças no modelo de leitura reproduzindo eventos históricos. Basicamente, as visualizações materializadas funcionam como um cache durável e somente leitura otimizado para consultas rápidas e eficientes.
Considerações sobre como combinar os padrões Event Sourcing e CQRS
Antes de combinar o padrão CQRS com o padrão Event Sourcing, avalie as seguintes considerações:
Consistência eventual: Como os armazenamentos de dados de escrita e leitura são separados, as atualizações para o armazenamento de dados de leitura podem atrasar em relação à geração de eventos. Este atraso resulta numa eventual coerência.
Maior complexidade: Combinar o padrão CQRS com o padrão Event Sourcing requer uma abordagem de design diferente, o que pode tornar uma implementação bem-sucedida mais desafiadora. Você deve escrever código para gerar, processar e manipular eventos e montar ou atualizar exibições para o modelo de leitura. No entanto, o padrão Event Sourcing simplifica a modelagem de domínio e permite que você reconstrua ou crie novas exibições facilmente, preservando o histórico e a intenção de todas as alterações de dados.
Desempenho da geração de visualizações: Gerar visualizações materializadas para o modelo de leitura pode consumir tempo e recursos significativos. O mesmo se aplica à projeção de dados através da reprodução e processamento de eventos para entidades ou coleções específicas. A complexidade aumenta quando os cálculos envolvem a análise ou soma de valores durante longos períodos, porque todos os eventos relacionados devem ser examinados. Implemente capturas instantâneas dos dados em intervalos regulares. Por exemplo, armazene o estado atual de uma entidade ou instantâneos periódicos de totais agregados, que é o número de vezes que uma ação específica ocorre. Os snapshots reduzem a necessidade de processar o histórico completo de eventos repetidamente, o que melhora o desempenho.
Exemplo
O código a seguir mostra extratos de um exemplo de uma implementação CQRS que usa definições diferentes para os modelos de leitura e de gravação. As interfaces de modelo não ditam os recursos dos armazenamentos de dados subjacentes e podem evoluir e ser ajustadas de forma independente porque essas interfaces são separadas.
O código seguinte mostra a definição do modelo de leitura.
// Query interface
namespace ReadModel
{
public interface ProductsDao
{
ProductDisplay FindById(int productId);
ICollection<ProductDisplay> FindByName(string name);
ICollection<ProductInventory> FindOutOfStockProducts();
ICollection<ProductDisplay> FindRelatedProducts(int productId);
}
public class ProductDisplay
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal UnitPrice { get; set; }
public bool IsOutOfStock { get; set; }
public double UserRating { get; set; }
}
public class ProductInventory
{
public int Id { get; set; }
public string Name { get; set; }
public int CurrentStock { get; set; }
}
}
O sistema permite aos utilizadores classificar os produtos. O código do aplicativo faz isso usando o RateProduct
comando mostrado no código a seguir.
public interface ICommand
{
Guid Id { get; }
}
public class RateProduct : ICommand
{
public RateProduct()
{
this.Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public int ProductId { get; set; }
public int Rating { get; set; }
public int UserId {get; set; }
}
O sistema usa a ProductsCommandHandler
classe para manipular comandos que o aplicativo envia. Por norma, os clientes enviam comandos para o domínio através de um sistema de mensagens, tal como uma fila. O processador de comandos aceita estes comandos e invoca métodos da interface de domínio. A granularidade de cada comando é concebida para reduzir a possibilidade de pedidos em conflito. O código seguinte mostra uma descrição da classe ProductsCommandHandler
.
public class ProductsCommandHandler :
ICommandHandler<AddNewProduct>,
ICommandHandler<RateProduct>,
ICommandHandler<AddToInventory>,
ICommandHandler<ConfirmItemShipped>,
ICommandHandler<UpdateStockFromInventoryRecount>
{
private readonly IRepository<Product> repository;
public ProductsCommandHandler (IRepository<Product> repository)
{
this.repository = repository;
}
void Handle (AddNewProduct command)
{
...
}
void Handle (RateProduct command)
{
var product = repository.Find(command.ProductId);
if (product != null)
{
product.RateProduct(command.UserId, command.Rating);
repository.Save(product);
}
}
void Handle (AddToInventory command)
{
...
}
void Handle (ConfirmItemsShipped command)
{
...
}
void Handle (UpdateStockFromInventoryRecount command)
{
...
}
}
Próximo passo
As informações a seguir podem ser relevantes quando você implementa esse padrão:
- As diretrizes de particionamento de dados descrevem as práticas recomendadas sobre como dividir dados em partições que você pode gerenciar e acessar separadamente para melhorar a escalabilidade, reduzir a contenção e otimizar o desempenho.
Recursos relacionados
Padrão de Event Sourcing. Esse padrão descreve como simplificar tarefas em domínios complexos e melhorar o desempenho, a escalabilidade e a capacidade de resposta. Ele também explica como fornecer consistência para dados transacionais enquanto mantém trilhas de auditoria completas e histórico que podem permitir ações de compensação.
Padrão de Vista Materializada. Esse padrão cria exibições pré-preenchidas, conhecidas como exibições materializadas, para consultas eficientes e extração de dados de um ou mais armazenamentos de dados. O modelo de leitura de uma implementação do CQRS pode conter vistas materializadas dos dados do modelo de escrita, ou o modelo de leitura pode ser usado para gerar vistas materializadas.