Padrão CQRS (Segregação de Responsabilidade de Consulta e Comando)Command and Query Responsibility Segregation (CQRS) pattern

O padrão CQRS (Command and Query Responsibility Segregation) separa as operações de leitura e atualização de um armazenamento de dados.The Command and Query Responsibility Segregation (CQRS) pattern separates read and update operations for a data store. A implementação de CQRS em seu aplicativo pode maximizar seu desempenho, escalabilidade e segurança.Implementing CQRS in your application can maximize its performance, scalability, and security. A flexibilidade criada pela migração para o CQRS permite que um sistema evolua melhor ao longo do tempo e impede que comandos de atualização causem conflitos de mesclagem no nível de domínio.The flexibility created by migrating to CQRS allows a system to better evolve over time and prevents update commands from causing merge conflicts at the domain level.

O problemaThe problem

Nas arquiteturas tradicionais, o mesmo modelo de dados é usado para consultar e atualizar um banco de dados.In traditional architectures, the same data model is used to query and update a database. É simples e funciona bem para operações CRUD básicas.That's simple and works well for basic CRUD operations. Em aplicativos mais complexos, no entanto, essa abordagem pode se tornar complicada.In more complex applications, however, this approach can become unwieldy. Por exemplo, no lado de leitura, o aplicativo pode executar muitas consultas diferentes, retornando objetos de transferência de dados (DTOs) com formas diferentes.For example, on the read side, the application may perform many different queries, returning data transfer objects (DTOs) with different shapes. O mapeamento de objetos pode se tornar complicado.Object mapping can become complicated. No lado da gravação, o modelo pode implementar uma validação complexa e lógica de negócios.On the write side, the model may implement complex validation and business logic. Como resultado, você pode terminar com um modelo excessivamente complexo que faz coisas em excesso.As a result, you can end up with an overly complex model that does too much.

As cargas de trabalho de leitura e gravação são muitas vezes assimétricas, com requisitos de desempenho e escala muito diferentes.Read and write workloads are often asymmetrical, with very different performance and scale requirements.

Uma arquitetura CRUD tradicional

  • Muitas vezes há uma incompatibilidade entre as representações de leitura e gravação dos dados, como colunas adicionais ou propriedades que devem ser atualizadas corretamente, mesmo que não sejam necessárias como parte de uma operação.There is often a mismatch between the read and write representations of the data, such as additional columns or properties that must be updated correctly even though they aren't required as part of an operation.

  • A contenção de dados pode ocorrer quando as operações são realizadas em paralelo no mesmo conjunto de dados.Data contention can occur when operations are performed in parallel on the same set of data.

  • A abordagem tradicional pode ter um efeito negativo sobre o desempenho devido à carga no armazenamento de dados e na camada de acesso a dados, e a complexidade das consultas necessárias para recuperar informações.The traditional approach can have a negative effect on performance due to load on the data store and data access layer, and the complexity of queries required to retrieve information.

  • O gerenciamento de segurança e permissões pode se tornar complexo, pois cada entidade está sujeita a operações de leitura e gravação, o que pode expor dados no contexto errado.Managing security and permissions can become complex, because each entity is subject to both read and write operations, which might expose data in the wrong context.

SoluçãoSolution

O CQRS separa leituras e gravações em diferentes modelos, usando comandos para atualizar dados e consultas para ler dados.CQRS separates reads and writes into different models, using commands to update data, and queries to read data.

  • Os comandos devem ser baseados em tarefas, em vez de centrados nos dados.Commands should be task based, rather than data centric. ("Reserve quarto de hotel", não "definir ReservationStatus para Reservado").("Book hotel room", not "set ReservationStatus to Reserved").
  • Os comandos podem ser colocados em uma fila para processamento assíncrono, em vez de serem processados sincronizadamente.Commands may be placed on a queue for asynchronous processing, rather than being processed synchronously.
  • As consultas nunca modificam o banco de dados.Queries never modify the database. Uma consulta retorna um DTO que não encapsula qualquer conhecimento de domínio.A query returns a DTO that does not encapsulate any domain knowledge.

Os modelos podem então ser isolados, como mostrado no diagrama a seguir, embora isso não seja um requisito absoluto.The models can then be isolated, as shown in the following diagram, although that's not an absolute requirement.

Uma arquitetura CQRS básica

Ter modelos separados de consulta e atualização simplifica o design e a implementação.Having separate query and update models simplifies the design and implementation. No entanto, uma desvantagem é que o código CQRS não pode ser gerado automaticamente a partir de um esquema de banco de dados usando mecanismos de andaimes, como ferramentas O/RM.However, one disadvantage is that CQRS code can't automatically be generated from a database schema using scaffolding mechanisms such as O/RM tools.

Para maior isolamento, você pode separar fisicamente os dados de leitura de dados de gravação.For greater isolation, you can physically separate the read data from the write data. Nesse caso, o banco de dados de leitura pode usar seu próprio esquema de dados, otimizado para consultas.In that case, the read database can use its own data schema that is optimized for queries. Por exemplo, ele pode armazenar uma exibição materializada dos dados, para evitar mapeamentos de O/RM complexos ou junções complexas.For example, it can store a materialized view of the data, in order to avoid complex joins or complex O/RM mappings. Ele ainda pode usar um tipo diferente de armazenamento de dados.It might even use a different type of data store. Por exemplo, o banco de dados de gravação pode ser relacional, enquanto o banco de dados de leitura é um banco de dados de documento.For example, the write database might be relational, while the read database is a document database.

Se forem usados bancos de dados separados de leitura e gravação, eles devem ser mantidos em sincronia. Normalmente, isso é feito fazendo com que o modelo de gravação publique um evento sempre que ele atualiza o banco de dados.If separate read and write databases are used, they must be kept in sync. Typically this is accomplished by having the write model publish an event whenever it updates the database. A atualização do banco de dados e a publicação do evento devem ocorrer em uma única transação.Updating the database and publishing the event must occur in a single transaction.

Uma arquitetura CQRS com repositórios de leitura e gravação separados

O repositório de leitura pode ser uma réplica somente para leitura do repositório de gravação, ou repositórios de gravação e leitura podem ter uma estrutura completamente diferente.The read store can be a read-only replica of the write store, or the read and write stores can have a different structure altogether. O uso de várias réplicas somente de leitura pode aumentar o desempenho da consulta, especialmente em cenários distribuídos onde réplicas somente leitura estão localizadas perto das instâncias do aplicativo.Using multiple read-only replicas can increase query performance, especially in distributed scenarios where read-only replicas are located close to the application instances.

A separação dos repositórios de gravação e leitura também permite que cada um seja dimensionado adequadamente para corresponder à carga.Separation of the read and write stores also allows each to be scaled appropriately to match the load. Por exemplo, os repositórios de leitura normalmente encontram uma carga muito maior do que os repositórios de gravação.For example, read stores typically encounter a much higher load than write stores.

Algumas implementações do CQRS usam o padrão de Evento de Fornecimento.Some implementations of CQRS use the Event Sourcing pattern. Com esse padrão, o estado do aplicativo é armazenado como uma sequência de eventos.With this pattern, application state is stored as a sequence of events. Cada evento representa um conjunto de alterações nos dados.Each event represents a set of changes to the data. O estado atual foi criado pela repetição dos eventos.The current state is constructed by replaying the events. Em um contexto CQRS, um dos benefícios do fornecimento do evento é que os mesmos eventos podem ser usados para notificar outros componentes — em particular, para notificar o modelo de leitura.In a CQRS context, one benefit of Event Sourcing is that the same events can be used to notify other components — in particular, to notify the read model. O modelo de leitura usa os eventos para criar um instantâneo do estado atual, que é mais eficiente para consultas.The read model uses the events to create a snapshot of the current state, which is more efficient for queries. No entanto, o fornecimento de evento adiciona complexidade ao design.However, Event Sourcing adds complexity to the design.

Os benefícios do CQRS incluem:Benefits of CQRS include:

  • Escala independente.Independent scaling. O CQRS permite que as cargas de trabalho de leitura e gravação sejam dimensionadas de forma independente e pode resultar em menos contenções de bloqueio.CQRS allows the read and write workloads to scale independently, and may result in fewer lock contentions.
  • Esquemas de dados otimizados.Optimized data schemas. O lado de leitura pode usar um esquema que é otimizado para consultas, enquanto o lado de gravação usa um esquema que é otimizado para atualizações.The read side can use a schema that is optimized for queries, while the write side uses a schema that is optimized for updates.
  • Segurança:Security. É mais fácil garantir que apenas as entidades do direito de domínio estejam executando gravações nos dados.It's easier to ensure that only the right domain entities are performing writes on the data.
  • Separação de preocupações.Separation of concerns. Isolar os lados de leitura e gravação pode resultar em modelos mais flexíveis e sustentáveis.Segregating the read and write sides can result in models that are more maintainable and flexible. A maior parte da lógica de negócios complexa vai para o modelo de gravação.Most of the complex business logic goes into the write model. O modelo de leitura pode ser relativamente simples.The read model can be relatively simple.
  • Consultas mais simples.Simpler queries. Ao armazenar uma exibição materializada no banco de dados de leitura, o aplicativo poderá evitar junções complexas durante as consultas.By storing a materialized view in the read database, the application can avoid complex joins when querying.

Questões e considerações de implementaçãoImplementation issues and considerations

Alguns desafios para implementar esse padrão incluem:Some challenges of implementing this pattern include:

  • Complexidade.Complexity. A ideia básica do CQRS é simples.The basic idea of CQRS is simple. Mas isso poderá resultar em um design de aplicativo mais complexo, especialmente se eles incluírem o padrão Fornecimento de Eventos.But it can lead to a more complex application design, especially if they include the Event Sourcing pattern.

  • Mensagens.Messaging. Embora o CQRS não necessite de mensagens, é comum usar mensagens para comandos de processo e publicar eventos de atualização.Although CQRS does not require messaging, it's common to use messaging to process commands and publish update events. Neste caso, o aplicativo deve tratar as falhas de mensagem ou as mensagens duplicadas.In that case, the application must handle message failures or duplicate messages.

  • Consistência eventual.Eventual consistency. Se você separar os bancos de dados de leitura e de gravação, os dados de leitura poderão ficar obsoletos.If you separate the read and write databases, the read data may be stale. A loja de modelos de leitura deve ser atualizada para refletir alterações na loja do modelo de gravação, e pode ser difícil detectar quando um usuário emitiu uma solicitação com base em dados de leitura obsoletos.The read model store must be updated to reflect changes to the write model store, and it can be difficult to detect when a user has issued a request based on stale read data.

Quando usar esse padrãoWhen to use this pattern

Considere o CQRS para os seguintes cenários:Consider CQRS for the following scenarios:

  • Domínios colaborativos onde muitos usuários acessam os mesmos dados em paralelo.Collaborative domains where many users access the same data in parallel. O CQRS permite definir comandos com granularidade suficiente para minimizar conflitos de mesclagem no nível de domínio, e os conflitos que surgem podem ser mesclados pelo comando.CQRS allows you to define commands with enough granularity to minimize merge conflicts at the domain level, and conflicts that do arise can be merged by the command.

  • Interfaces de usuário baseadas em tarefas, onde os usuários são guiados por um processo complexo como uma série de etapas ou com modelos de domínio complexos.Task-based user interfaces where users are guided through a complex process as a series of steps or with complex domain models. O modelo de gravação tem uma pilha completa de processamento de comando com lógica de negócios, validação de entrada e validação de negócios.The write model has a full command-processing stack with business logic, input validation, and business validation. O modelo de gravação pode tratar um conjunto de objetos associados como uma única unidade para alterações de dados (um agregado, na terminologia DDD) e garantir que esses objetos estejam sempre em um estado consistente.The write model may treat a set of associated objects as a single unit for data changes (an aggregate, in DDD terminology) and ensure that these objects are always in a consistent state. O modelo de leitura não tem lógica de negócios ou pilha de validação, e apenas retorna um DTO para uso em um modelo de exibição.The read model has no business logic or validation stack, and just returns a DTO for use in a view model. O modelo de leitura é, eventualmente, consistente com o modelo de gravação.The read model is eventually consistent with the write model.

  • Cenários em que o desempenho das leituras de dados deve ser ajustado separadamente do desempenho das gravações de dados, especialmente quando o número de leituras é muito maior do que o número de gravações.Scenarios where performance of data reads must be fine tuned separately from performance of data writes, especially when the number of reads is much greater than the number of writes. Neste cenário, você pode dimensionar o modelo de leitura, mas executar o modelo de gravação em apenas alguns casos.In this scenario, you can scale out the read model, but run the write model on just a few instances. Um pequeno número de instâncias de modelo de gravação também ajuda a minimizar a ocorrência de conflitos de mesclagem.A small number of write model instances also helps to minimize the occurrence of merge conflicts.

  • Cenários onde uma equipe de desenvolvedores pode se concentrar no modelo de domínio complexo que faz parte do modelo de gravação e outra equipe pode se concentrar no modelo de leitura e nas interfaces de usuário.Scenarios where one team of developers can focus on the complex domain model that is part of the write model, and another team can focus on the read model and the user interfaces.

  • Cenários onde o sistema deve evoluir ao longo do tempo e pode conter várias versões do modelo, ou onde as regras de negócio mudam regularmente.Scenarios where the system is expected to evolve over time and might contain multiple versions of the model, or where business rules change regularly.

  • Integração com outros sistemas, especialmente em combinação com fornecimento de evento, onde a falha temporal de um subsistema não deve afetar a disponibilidade dos outros.Integration with other systems, especially in combination with event sourcing, where the temporal failure of one subsystem shouldn't affect the availability of the others.

Este padrão não é recomendado quando:This pattern isn't recommended when:

  • O domínio ou as regras de negócios são simples.The domain or the business rules are simple.

  • Uma simples interface de usuário no estilo CRUD e operações de acesso a dados são suficientes.A simple CRUD-style user interface and data access operations are sufficient.

Considere aplicar CQRS em seções limitadas do seu sistema, onde será mais valioso.Consider applying CQRS to limited sections of your system where it will be most valuable.

Fornecimento de evento e CQRSEvent Sourcing and CQRS

O padrão CQRS é frequentemente utilizado juntamente com o padrão de Fornecimento de Evento.The CQRS pattern is often used along with the Event Sourcing pattern. Os sistemas baseados em CQRS utilizam modelos de dados de gravação e leitura separados, cada um adaptado a tarefas relevantes e, muitas vezes, localizado em repositórios separados fisicamente.CQRS-based systems use separate read and write data models, each tailored to relevant tasks and often located in physically separate stores. Quando utilizado com o padrão Fornecimento de Evento, o repositório de eventos é o modelo de gravação e é a fonte oficial de informações.When used with the Event Sourcing pattern, the store of events is the write model, and is the official source of information. O modelo de leitura de um sistema baseado em CQRS fornece exibições materializadas dos dados, geralmente como exibições altamente desnormalizadas.The read model of a CQRS-based system provides materialized views of the data, typically as highly denormalized views. Essas exibições são adaptadas às interfaces e aos requisitos de exibição do aplicativo, o que ajuda a maximizar tanto o desempenho de consulta como exibição.These views are tailored to the interfaces and display requirements of the application, which helps to maximize both display and query performance.

Utilizando o stream de eventos como o repositório de gravação, em vez dos dados reais em um ponto no tempo, evita conflitos de atualização em um único agregado e maximiza o desempenho e a escalabilidade.Using the stream of events as the write store, rather than the actual data at a point in time, avoids update conflicts on a single aggregate and maximizes performance and scalability. Os eventos podem ser utilizados para gerar de maneira assíncrona exibições materializadas dos dados que são utilizadas para preencher o repositório de leitura.The events can be used to asynchronously generate materialized views of the data that are used to populate the read store.

Como o repositório de evento é a fonte oficial de informações, é possível excluir as exibições materializadas e reproduzir todos os eventos passados para criar uma nova representação do estado atual quando o sistema evoluir ou quando o modelo de leitura precisar alterar.Because the event store is the official source of information, it is possible to delete the materialized views and replay all past events to create a new representation of the current state when the system evolves, or when the read model must change. As exibições materializadas são efetivamente um cache somente leitura durável dos dados.The materialized views are in effect a durable read-only cache of the data.

Ao utilizar CQRS combinado com o padrão Fornecimento de Evento, considere o seguinte:When using CQRS combined with the Event Sourcing pattern, consider the following:

  • Como em qualquer sistema onde os repositórios de gravação e leitura são separados, os sistemas baseados nesse padrão são eventualmente consistentes.As with any system where the write and read stores are separate, systems based on this pattern are only eventually consistent. Haverá algum atraso entre o evento que estiver sendo gerado e o armazenamento de dados sendo atualizado.There will be some delay between the event being generated and the data store being updated.

  • O padrão adiciona complexidade porque o código deve ser criado para iniciar e tratar eventos e montar ou atualizar as exibições ou objetos apropriados exigidos por consultas ou um modelo de leitura.The pattern adds complexity because code must be created to initiate and handle events, and assemble or update the appropriate views or objects required by queries or a read model. A complexidade do padrão CQRS quando utilizado com o padrão de Fornecimento de Evento pode dificultar a implementação bem-sucedida e requer uma abordagem diferente para a concepção de sistemas.The complexity of the CQRS pattern when used with the Event Sourcing pattern can make a successful implementation more difficult, and requires a different approach to designing systems. No entanto, o fornecimento de evento pode tornar mais fácil para modelar o domínio e facilitar para recompilar exibições ou criar novas porque a intenção das alterações nos dados é preservada.However, event sourcing can make it easier to model the domain, and makes it easier to rebuild views or create new ones because the intent of the changes in the data is preserved.

  • A geração de exibições materializadas para uso no modelo de leitura ou projeções dos dados, reproduzindo e manipulando os eventos para entidades específicas ou coleções de entidades pode exigir um tempo de processamento significativo e uso de recursos.Generating materialized views for use in the read model or projections of the data by replaying and handling the events for specific entities or collections of entities can require significant processing time and resource usage. Isto é especialmente verdadeiro se requer soma ou análise de valores em longos períodos, pois poderá ser necessário examinar todos os eventos associados.This is especially true if it requires summation or analysis of values over long periods, because all the associated events might need to be examined. Resolva isso implementando instantâneos dos dados em intervalos agendados, como uma contagem total do número de uma ação específica que ocorreu ou o estado atual de uma entidade.Resolve this by implementing snapshots of the data at scheduled intervals, such as a total count of the number of a specific action that have occurred, or the current state of an entity.

ExemploExample

O código a seguir mostra alguns extratos de um exemplo de uma implementação CQRS que utiliza diferentes definições para os modelos de leitura e gravação.The following code shows some extracts from an example of a CQRS implementation that uses different definitions for the read and the write models. As interfaces modelo não ditarão quaisquer recursos dos repositórios de dados subjacentes, e elas podem evoluir e terem ajustes finos de maneira independente porque essas interfaces estão separadas.The model interfaces don't dictate any features of the underlying data stores, and they can evolve and be fine-tuned independently because these interfaces are separated.

O código a seguir mostra a definição do modelo de leitura.The following code shows the read model definition.

// 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 usuários avaliar produtos.The system allows users to rate products. O código do aplicativo faz isso utilizando o comando RateProduct mostrado no código a seguir.The application code does this using the RateProduct command shown in the following code.

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 utiliza a classe ProductsCommandHandler para lidar com comandos enviados pelo aplicativo.The system uses the ProductsCommandHandler class to handle commands sent by the application. Normalmente, os clientes enviam comandos para o domínio através de um sistema de mensagens, como uma fila.Clients typically send commands to the domain through a messaging system such as a queue. O manipulador de comando aceita esses comandos e invoca métodos da interface de domínio.The command handler accepts these commands and invokes methods of the domain interface. A granularidade de cada comando é projetada para reduzir a chance de solicitações conflitantes.The granularity of each command is designed to reduce the chance of conflicting requests. O código a seguir mostra uma estrutura de tópicos da classe ProductsCommandHandler.The following code shows an outline of the ProductsCommandHandler class.

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)
  {
    ...
  }
}

Os seguintes padrões e diretrizes serão úteis ao implementar esse padrão:The following patterns and guidance are useful when implementing this pattern:

  • Primer de consistência de dados.Data Consistency Primer. Explica os problemas que normalmente são encontrados devido à coerência eventual entre os repositórios de dados de gravação e leitura ao utilizar o padrão CQRS e como esses problemas podem ser resolvidos.Explains the issues that are typically encountered due to eventual consistency between the read and write data stores when using the CQRS pattern, and how these issues can be resolved.

  • Orientação de particionamento de dados.Data Partitioning Guidance. Descreve as melhores práticas para dividir dados em partições que podem ser gerenciadas e acessadas separadamente para melhorar a escalabilidade, reduzir a contenção e otimizar o desempenho.Describes best practices for dividing data into partitions that can be managed and accessed separately to improve scalability, reduce contention, and optimize performance.

  • Padrão de Fornecimento do Evento.Event Sourcing pattern. Descreve detalhadamente como o Fornecimento de Eventos pode ser utilizado com o padrão CQRS para simplificar tarefas em domínios complexos, ao mesmo tempo em que melhora o desempenho, a escalabilidade e capacidade de resposta.Describes in more detail how Event Sourcing can be used with the CQRS pattern to simplify tasks in complex domains while improving performance, scalability, and responsiveness. Além disso, como fornecer consistência para dados transacionais, ao mesmo tempo que mantém trilhas de auditoria completas e histórico que podem permitir ações de compensação.As well as how to provide consistency for transactional data while maintaining full audit trails and history that can enable compensating actions.

  • Padrão de exibição materializado.Materialized View pattern. O modelo de leitura de uma implementação CQRS pode conter exibições materializadas dos dados do modelo de gravação, ou o modelo de leitura pode ser utilizado para gerar exibições materializadas.The read model of a CQRS implementation can contain materialized views of the write model data, or the read model can be used to generate materialized views.

  • Guia de padrões e práticas Recurso CQRS.The patterns & practices guide CQRS Journey. Em particular, introduzir o padrão de segregação de responsabilidade da consulta de comando explora o padrão e quando é útil, e Epílogo: Lições aprendidas ajudam você a entender algumas das questões que surgem ao usar esse padrão.In particular, Introducing the Command Query Responsibility Segregation pattern explores the pattern and when it's useful, and Epilogue: Lessons Learned helps you understand some of the issues that come up when using this pattern.

  • A postagem CQRS por Martin Fowler, que explica os conceitos básicos do padrão e links para outros recursos úteis.The post CQRS by Martin Fowler, which explains the basics of the pattern and links to other useful resources.