コマンド クエリ責務分離 (CQRS) パターンCommand and Query Responsibility Segregation (CQRS) pattern

コマンド クエリ責務分離 (CQRS) パターンは、データ ストアの読み取り操作と更新操作を分離します。The Command and Query Responsibility Segregation (CQRS) pattern separates read and update operations for a data store. アプリケーション内に CQRS を実装すると、そのパフォーマンス、スケーラビリティ、セキュリティが最大化される場合があります。Implementing CQRS in your application can maximize its performance, scalability, and security. CQRS への移行によって生まれる柔軟性により、システムは時間の経過と共にさらに進化し、更新コマンドでドメイン レベルのマージ競合が発生することを防ぐことができます。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.

問題The problem

従来のアーキテクチャでは、データベースの更新とクエリに同じデータ モデルが使用されます。In traditional architectures, the same data model is used to query and update a database. このシンプルな方法は、基本的な CRUD 操作に適しています。That's simple and works well for basic CRUD operations. ただし、複雑なアプリケーションの場合、このアプローチではうまくいかないことがあります。In more complex applications, however, this approach can become unwieldy. たとえば、読み取り側でさまざまなクエリが実行され、形式の異なる複数のデータ転送オブジェクト (DTO) が返される場合もあります。For example, on the read side, the application may perform many different queries, returning data transfer objects (DTOs) with different shapes. これにより、オブジェクトのマッピングが複雑になる可能性があります。Object mapping can become complicated. また書き込み側のモデルでは、複雑な検証とビジネス ロジックが実装される可能性があります。On the write side, the model may implement complex validation and business logic. その結果、モデルが複雑になりすきる恐れがあります。As a result, you can end up with an overly complex model that does too much.

読み取りと書き込みのワークロードが不均衡になりやすいため、パフォーマンスやスケールの要件が大きく異なってくる可能性があります。Read and write workloads are often asymmetrical, with very different performance and scale requirements.

従来の CRUD アーキテクチャ

  • 読み取りと書き込みのデータ表現が一致しないことがよくあります。具体的には、操作の一部としては必要ないものの、正しく更新しなければならない追加の列やプロパティなどです。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.

  • 同じデータ セットに対して操作が並列で実行されると、データ競合が発生する可能性があります。Data contention can occur when operations are performed in parallel on the same set of data.

  • 従来のアプローチでは、データ ストアとデータ アクセス層への負荷、および情報を取得するために必要なクエリの複雑さによって、パフォーマンスに悪影響を及ぼす可能性があります。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.

  • 各エンティティは読み取りと書き込みの両方の操作の対象となるため、セキュリティとアクセス許可の管理が複雑化する可能性があります。これにより、誤ったコンテキストでデータが公開されることがあります。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.

解決策Solution

CQRS では、データを更新するためのコマンドとデータを読み取るためのクエリを使用し、読み取りと書き込みを別々のモデルに分離します。CQRS separates reads and writes into different models, using commands to update data, and queries to read data.

  • コマンドは、データ中心ではなく、タスクベースにしますCommands should be task based, rather than data centric. (「ReservationStatus を Reserved に設定する」などではなく、「ホテルの部屋を予約する」などの形式にします)。("Book hotel room", not "set ReservationStatus to Reserved").
  • コマンドは、同期的に処理するのではなく、非同期処理のキューに配置できます。Commands may be placed on a queue for asynchronous processing, rather than being processed synchronously.
  • クエリでは、データベースは変更されません。Queries never modify the database. クエリでは、ドメイン ナレッジをカプセル化しない DTO が返されます。A query returns a DTO that does not encapsulate any domain knowledge.

絶対条件ではありませんが、次の図に示すようにモデルを分離できます。The models can then be isolated, as shown in the following diagram, although that's not an absolute requirement.

基本的な CQRS アーキテクチャ

クエリと更新のモデルを別々にすることで、設計と実装が簡単になります。Having separate query and update models simplifies the design and implementation. ただし、O/RM ツールなどのスキャフォールディング メカニズムを使用してデータベース スキーマから CQRS コードを自動的に生成できないという欠点があります。However, one disadvantage is that CQRS code can't automatically be generated from a database schema using scaffolding mechanisms such as O/RM tools.

分離性を高めるために、読み取りデータと書き込みデータを物理的に分離することもできます。For greater isolation, you can physically separate the read data from the write data. その場合は、読み取りデータベースでは、クエリ用に最適化された独自のデータ スキーマを使用できます。In that case, the read database can use its own data schema that is optimized for queries. たとえば、結合や O/RM マッピングが複雑になるのを回避するために、データの具体化されたビューを格納することもできます。For example, it can store a materialized view of the data, in order to avoid complex joins or complex O/RM mappings. また、異なる種類のデータ ストアを使用することもできます。It might even use a different type of data store. たとえば、書き込みデータベースをリレーショナルにし、読み取りデータベースをドキュメント データベースにすることもできます。For example, the write database might be relational, while the read database is a document database.

読み取りデータベースと書き込みデータベースを個別に使用する場合は、両者の同期を維持する必要があります。これは通常、データベースの更新時に書き込みモデルでイベントを発行することによって達成されます。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. データベースの更新とイベントの発行は、単一のトランザクションで行う必要があります。Updating the database and publishing the event must occur in a single transaction.

読み取りストアと書き込みストアを分けた CQRS アーキテクチャ

書き込みストアの読み取り専用レプリカを読み取りストアにすることも、読み取りストアと書き込みストアをまったく別の構造にすることもできます。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. 読み取り専用レプリカを複数使用すると、読み取り専用レプリカがアプリケーション インスタンスの近くに配置されている分散シナリオでは特に、クエリのパフォーマンスが向上します。Using multiple read-only replicas can increase query performance, especially in distributed scenarios where read-only replicas are located close to the application instances.

読み取りストアと書き込みストアを分離することにより、それぞれの負荷に合わせて適切にスケーリングすることもできます。Separation of the read and write stores also allows each to be scaled appropriately to match the load. たとえば、読み取りストアには通常、書き込みストアよりはるかに高い負荷が発生します。For example, read stores typically encounter a much higher load than write stores.

CQRS の実装では、イベント ソーシング パターンが使用される場合があります。Some implementations of CQRS use the Event Sourcing pattern. このパターンを使用すると、アプリケーションの状態が一連のイベントとして格納されます。With this pattern, application state is stored as a sequence of events. 各イベントは、データに対する一連の変更を表します。Each event represents a set of changes to the data. 現在の状態は、これらのイベントを再生することによって構築されます。The current state is constructed by replaying the events. CQRS の場合、イベント ソーシングの利点の 1 つは、他のコンポーネントへの通知 (特に、読み取りモデルへの通知) に、同じイベントを使用できることです。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. 読み取りモデルでは、現在の状態のスナップショットを作成するのにイベントが使用されます (そのほうが、クエリにとってより効率的です)。The read model uses the events to create a snapshot of the current state, which is more efficient for queries. ただし、イベント ソーシングを使用すると、設計がより複雑になります。However, Event Sourcing adds complexity to the design.

CQRS の利点は次のとおりです。Benefits of CQRS include:

  • 独立したスケーリングIndependent scaling. CQRS では、読み取りと書き込みの各ワークロードを個別にスケーリングできるので、ロック競合を減らせる可能性があります。CQRS allows the read and write workloads to scale independently, and may result in fewer lock contentions.
  • 最適化されたデータ スキーマOptimized data schemas. 読み取り側ではクエリ用に最適化されたスキーマを使用し、書き込み側では更新用に最適化されたスキーマを使用できます。The read side can use a schema that is optimized for queries, while the write side uses a schema that is optimized for updates.
  • セキュリティSecurity. 適切なドメイン エンティティだけがデータへの書き込みを実行している状態を維持しやすくなります。It's easier to ensure that only the right domain entities are performing writes on the data.
  • 懸念事項の分離Separation of concerns. 読み取り側と書き込み側を分離することで、モデルの保守性と柔軟性を向上できる可能性があります。Segregating the read and write sides can result in models that are more maintainable and flexible. 複雑なビジネス ロジックの多くは、書き込みモデルになります。Most of the complex business logic goes into the write model. 読み取りモデルは、比較的シンプルにすることができます。The read model can be relatively simple.
  • クエリがよりシンプルSimpler queries. 具体化されたビューを読み取りデータベースに格納することで、クエリ時の複雑な結合を回避できます。By storing a materialized view in the read database, the application can avoid complex joins when querying.

実装に関する問題と注意事項Implementation issues and considerations

このパターンの実装には、次のような課題があります。Some challenges of implementing this pattern include:

  • 複雑さComplexity. CQRS の基本的な考え方はシンプルです。The basic idea of CQRS is simple. ただし、アプリケーションの設計は複雑になる可能性があります。このことは、イベント ソーシング パターンが含まれる場合には特に顕著です。But it can lead to a more complex application design, especially if they include the Event Sourcing pattern.

  • メッセージングMessaging. CQRS ではメッセージングは必須ではありませんが、コマンドの発行やイベントの更新を処理するためにメッセージングが使用されることもよくあります。Although CQRS does not require messaging, it's common to use messaging to process commands and publish update events. その場合には、メッセージのエラーや重複を処理する必要が生じます。In that case, the application must handle message failures or duplicate messages.

  • 最終的な一貫性Eventual consistency. 読み取りデータベースと書き込みデータベースを分割すると、読み取りデータが古くなる可能性があります。If you separate the read and write databases, the read data may be stale. 読み取りモデル ストアは、書き込みモデル ストアへの変更を反映させるために更新する必要がありますが、ユーザーが古い読み取りデータに基づいた要求をいつ発行したのかを検出するのは容易でない場合があります。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.

このパターンを使用する状況When to use this pattern

次のシナリオで CQRS を考えてみましょう。Consider CQRS for the following scenarios:

  • 多くのユーザーが同じデータに並行してアクセスするコラボレーション ドメイン。Collaborative domains where many users access the same data in parallel. CQRS を使用すると、ドメイン レベルでマージの競合を最小化するのに十分な細分性でコマンドを定義でき、発生した競合はコマンドでマージできます。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.

  • 一連の手順として、または複雑なドメイン モデルを使用して、複雑なプロセスがユーザーにされるタスクベースのユーザー インターフェイス。Task-based user interfaces where users are guided through a complex process as a series of steps or with complex domain models. 書き込みモデルには、ビジネス ロジック、入力検証、およびビジネス検証を含む完全なコマンド処理スタックがあります。The write model has a full command-processing stack with business logic, input validation, and business validation. 書き込みモデルでは、関連付けられたオブジェクトのセットをデータ変更のための 1 つの単位 (DDD の用語で集計) として処理し、これらのオブジェクトが常に一貫性のある状態になるようにすることができます。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. 読み取りモデルにはビジネス ロジックや検証スタックはなく、ビュー モデルで使用する DTO を返すだけです。The read model has no business logic or validation stack, and just returns a DTO for use in a view model. 読み取りモデルは、最終的には書き込みモデルと一致します。The read model is eventually consistent with the write model.

  • データ読み取りのパフォーマンスを、データ書き込みのパフォーマンスとは別に細かく調整する必要があるシナリオ (特に、読み取り回数が書き込み回数より非常に多い場合)。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. このシナリオでは、読み取りモデルをスケールアウトするが、書き込みモデルは少数のインスタンスで実行できます。In this scenario, you can scale out the read model, but run the write model on just a few instances. 書き込みモデルのインスタンス数を少なくすることは、マージ競合の発生を最小化するうえでも役立ちます。A small number of write model instances also helps to minimize the occurrence of merge conflicts.

  • 1 つの開発者チームが書き込みモデルの一部である複雑なドメイン モデルに注力し、もう 1 つのチームが読み取りモデルとユーザー インターフェイスに注力できるシナリオ。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.

  • システムが時間の経過に伴って進化し、モデルの複数のバージョンを含むようになることが予測されるシナリオや、ビジネス ルールが定期的に変更されるシナリオ。Scenarios where the system is expected to evolve over time and might contain multiple versions of the model, or where business rules change regularly.

  • 他のシステムとの統合 (特にイベント ソーシングとの組み合わせ)。この場合、1 つのサブシステムの一時的なエラーは、他のサブシステムの可用性に影響しません。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.

このパターンは次の場合は推奨されません。This pattern isn't recommended when:

  • ドメインやビジネス ルールが単純である。The domain or the business rules are simple.

  • 単純な CRUD スタイルのユーザー インターフェイスとデータ アクセス操作で十分である。A simple CRUD-style user interface and data access operations are sufficient.

システムの最も重要な、限られたセクションに CQRS を適用することを検討してください。Consider applying CQRS to limited sections of your system where it will be most valuable.

イベント ソーシングと CQRSEvent Sourcing and CQRS

CQRS パターンは、イベント ソーシング パターンと共によく使用されます。The CQRS pattern is often used along with the Event Sourcing pattern. CQRS ベースのシステムは、個別の読み取りデータ モデルと書き込みデータ モデルを使用します。これらはそれぞれ関連するタスクに合わせて調整されており、多くの場合、物理的に分離されたストアに存在します。CQRS-based systems use separate read and write data models, each tailored to relevant tasks and often located in physically separate stores. イベント ソーシング パターンと共に使用される場合、イベントのストアは書き込みモデルであり、公式の情報ソースです。When used with the Event Sourcing pattern, the store of events is the write model, and is the official source of information. CQRS ベースのシステムの読み取りモデルは、データの具体化されたビュー (通常は高度に非正規化されたビュー) を提供します。The read model of a CQRS-based system provides materialized views of the data, typically as highly denormalized views. これらのビューは、アプリケーションのインターフェイスとディスプレイの要件に合わせて調整されており、ディスプレイとクエリの両方のパフォーマンスを最大化するのに役立ちます。These views are tailored to the interfaces and display requirements of the application, which helps to maximize both display and query performance.

ある時点での実際のデータではなく、イベントのストリームを書き込みストアとして使用することにより、単一の集計での更新の競合を回避し、パフォーマンスとスケーラビリティを最大化します。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. イベントを使用して、読み取りストアへのデータ入力に使用されるデータの具体化されたビューを非同期的に生成できます。The events can be used to asynchronously generate materialized views of the data that are used to populate the read store.

イベント ストアは公式の情報ソースであるため、システムが進化したり読み取りモデルの変更が必要になったりした場合に、具体化されたビューを削除し、過去のすべてのイベントを再生して最新の状態の新しい表現を作成することができます。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. 具体化されたビューは、実質的にはデータの持続的な読み取り専用キャッシュです。The materialized views are in effect a durable read-only cache of the data.

CQRS とイベント ソーシング パターンを組み合わせて使用する場合、次の点を考慮してください。When using CQRS combined with the Event Sourcing pattern, consider the following:

  • 書き込みストアと読み取りストアが分離しているすべてのシステムと同様に、このパターンに基づくシステムは、最後の段階にならないと一貫性が確保されません。As with any system where the write and read stores are separate, systems based on this pattern are only eventually consistent. イベントの生成とデータ ストアの更新の間には、いくらかの遅延があります。There will be some delay between the event being generated and the data store being updated.

  • イベントを開始および処理し、クエリや読み取りモデルで必要な適切なビューやオブジェクトをアセンブルまたは更新するようにコードを作成する必要があるため、このパターンでは複雑さが増します。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. CQRS パターンをイベント ソーシング パターンと併用すると複雑さが増すため、実装が難しくなる可能性があり、システム設計に別のアプローチが必要になります。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. ただし、イベント ソーシングを使用するとドメインのモデル化が容易になります。また、データの変更の目的が保持されるため、ビューの再構築や新規作成も容易になります。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.

  • 特定のエンティティまたはエンティティのコレクションのイベントを再生または処理することにより、データの読み取りモデルまたはプロジェクションで使用する具体化されたビューを生成すると、大量の処理時間とリソース使用量が必要になる可能性があります。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. これは特に、長期にわたる値の合計や解析が必要な場合に当てはまります。関連するすべてのイベントの検証が必要な場合があるためです。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. この問題を解決するには、スケジュールされた間隔 (発生した特定のアクションの合計数、エンティティの現在の状態など) でデータのスナップショットを実装します。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.

Example

次のコードは、読み取りモデルと書き込みモデルに異なる定義を使用する CQRS 実装の例から抽出したものです。The following code shows some extracts from an example of a CQRS implementation that uses different definitions for the read and the write models. モデル インターフェイスは、基になるデータ ストアの機能に影響しません。また、進化することができ、インターフェイスどうしが分離しているため個別に微調整もできます。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.

次のコードは、読み取りモデルの定義を示しています。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; }
  }
}

ユーザーは製品を評価することができます。The system allows users to rate products. そのためには、次のコードに示すように、アプリケーション コードで RateProduct コマンドを使用します。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; }
}

システムは ProductsCommandHandler クラスを使用して、アプリケーションから送信されたコマンドを処理します。The system uses the ProductsCommandHandler class to handle commands sent by the application. クライアントは通常、キューなどのメッセージング システムを使用して、ドメインにコマンドを送信します。Clients typically send commands to the domain through a messaging system such as a queue. コマンド ハンドラーはこれらのコマンドを受け入れ、ドメイン インターフェイスのメソッドを呼び出します。The command handler accepts these commands and invokes methods of the domain interface. 各コマンドの細分性は、要求の競合が発生する可能性が少なくなるように設計されています。The granularity of each command is designed to reduce the chance of conflicting requests. 次のコードは、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)
  {
    ...
  }
}

このパターンを実装する場合、次のパターンとガイダンスが役に立ちます。The following patterns and guidance are useful when implementing this pattern:

  • Data consistency primer (データ整合性入門)Data Consistency Primer. CQRS パターン使用時に読み取りデータ ストアと書き込みデータ ストアの間の最終的な整合性が原因で通常発生する問題と、これらの問題の解決方法について説明します。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.

  • データのパーティション分割のガイダンスData Partitioning Guidance. 個別に管理およびアクセス可能なパーティションにデータを分割して、スケーラビリティの向上、競合の削減、パフォーマンスの最適化を図るベスト プラクティスについて説明します。Describes best practices for dividing data into partitions that can be managed and accessed separately to improve scalability, reduce contention, and optimize performance.

  • イベント ソーシング パターンEvent Sourcing pattern. イベント ソーシングを CQRS パターンと共に使用して、複雑なドメインでのタスクを簡略化しながら、パフォーマンス、スケーラビリティ、応答性を向上させる方法について、さらに詳しく説明します。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. 補正アクションを有効にできる完全な監査証跡と履歴を保持しながら、トランザクション データの整合性を提供する方法についても説明します。As well as how to provide consistency for transactional data while maintaining full audit trails and history that can enable compensating actions.

  • Materialized View Pattern (具体化されたビュー パターン)Materialized View pattern. CQRS 実装の読み取りモデルには、書き込みモデル データの具体化されたビューを含めることができます。また、読み取りモデルは具体化されたビューの生成に使用できます。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.

  • パターンとプラクティスのガイド「CQRS Journey (CQRS の旅)」。The patterns & practices guide CQRS Journey. 具体的には、コマンド クエリ責務分離パターンの概要に関するページで、パターンとそのパターンが役立つ状況について説明します。エピローグのLessons Learned (エピローグ: 得られた教訓)」は、このパターンを使用したときに発生する問題の一部を理解するのに役立ちます。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.

  • Martin Fowler の投稿「CQRS」では、パターンの基本と他の有用なリソースへのリンクを紹介しています。The post CQRS by Martin Fowler, which explains the basics of the pattern and links to other useful resources.