Cutting Edge
一般的なアプリケーション向けの CQRS
ドメイン駆動設計 (DDD) は約 10 年前に登場し、ソフトウェアの開発者やアーキテクトに影響を与えています。具体的なメリットやデメリットはともかく、DDD は、オブジェクト指向パラダイムが初めて唱えられた頃、開発者の誰もが夢見たことを具体化します。つまり、包括的なオブジェクト モデルを中心にアプリケーションを構築して、すべての関係者の要件や懸案事項に対処するという夢です。
その後の 10 年、多くの開発者が DDD のガイドラインに従うプロジェクトに取り組んできました。成功したプロジェクトもあれば、失敗したプロジェクトもあります。実は、ソフトウェア システムの機能面にも非機能面にも対処する包括的なプロジェクト モデルというものは、素晴らしい理想郷にすぎません。高度なユーザー エクスペリエンスが求められ、ビジネス モデルが絶えず変化し、要件が目まぐるしく変わる今日では特に、堅牢かつ安定したオブジェクト モデルはほぼ幻想と言えます。
最近は、コマンド クエリ責務分離 (CQRS: Command and Query Responsibility Segregation) という新たな考え方が勢いを増し始めています。CQRS は、ソフトウェアの専門家に与えられる優秀な最新の道具ではありません。さまざまな場面で例として取り上げられているほど複雑なものでもありません。簡単に言えば、CQRS とは具体的な実装パターンで、想定される存続期間や複雑性とは関係なく、おそらく、ほぼすべての種類のソフトウェア アプリケーションにとって最も適切なパターンです。
CQRS を当てはめる方法は 1 つだけではありません。少なく見ても 3 つの異なる種類があります。ホテルの客室や飲み物風にニックネームを付けるとしたら、レギュラー、プレミアム、デラックスの 3 種類です。CQRS の例や記事を簡単に検索してみると、ほとんどがデラックスに分類されるものです。これは実用には複雑過ぎ、多くの一般的なアプリケーションにとっては行き過ぎです。
CQRS は、プロジェクトの複雑さとは関係なく効果を発揮するソフトウェア開発のアプローチです。結局のところ、一般的なアプリケーション向けの CQRS とは、従来の多層アーキテクチャを単純に改訂したものです。多層アーキテクチャは、現在も大きな変化を受け入れ、進化を続けています。
コマンドとクエリ
1980 年代後半、プログラミング言語 Eiffel を考案した Bertrand Meyer は、ソフトウェアにはシステム状態を変更するコマンドと、システム状態を読み取るクエリがあるだけだと結論付けました。ソフトウェアのすべてのステートメントはコマンドかクエリのいずれかで、両者を組み合わせたものではありません。つまり、「質問することで答えを変えてはいけません」CQRS とはこの考え方を今風に言い直したものです。つまり、「コマンドとクエリは別物で、個別に実装すべきもの」です。
操作の 2 つのグループが同じプログラミング スタックや同じモデルを使用するように強制されている場合、コマンドとクエリ間の論理的な分離はあまり顕在化しません。これは複雑なビジネス シナリオには特に当てはまります。オブジェクト モデルであれ、機能モデルであれ、単一のモデルはすぐに管理不能に陥ります。単一のモデルは、飛躍的に大きく複雑になり、時間や予算を浪費するようになりますが、思い通りに機能することはありません。
CQRS によって行われる分離は、ある層にクエリ操作を、別の層にコマンドをそれぞれグループ化することによって実現されます。各層にはデータの独自のモデルとサービスの独自のセットがあり、パターンとテクノロジの独自の組み合わせを使用して各層が構築されます。さらに重要なことに、この 2 つの層はまったく異なる物理層に存在でき、相互に干渉することなく個別に最適化されます。図 1 は CQRS アーキテクチャの基礎を示しています。
図 1 標準の多層 CQRS アーキテクチャ
簡単に考えると、コマンドとクエリは別物で、ソフトウェア アーキテクチャに大きな影響を与えるものです。たとえば、各ドメイン層について考え、コーディングするのは非常に簡単です。コマンド スタックのドメイン層では、タスクを実行するために必要なデータ規則、ビジネス規則、およびセキュリティ規則のみを考えます。これに対して、クエリ スタックのドメイン層は、ごくシンプルに ADO.NET 接続経由で直接 SQL クエリを実行するだけです。
プレゼンテーション層の入り口でセキュリティ チェックを行う場合、クエリ スタックは Entity Framework を囲む薄いラッパーとなるか、データのクエリに使用する何かになります。各ドメイン層内では、多様化するプレゼンテーションやビジネスのニーズに合わせてデータを重複または複製することなく、ドメインのニーズに合わせてデータを自由に形作ることができます。
DDD が初めて登場したころ、DDD はソフトウェア開発の核心にある複雑さに対処するものでした。しかし、これを実践していく過程でさまざまな複雑さに直面しました。複雑さの多くは、単純にビジネス ドメインに含まれていると考えられていました。しかし、ほとんどの複雑さは、クエリとコマンドのデカルト積によって求められます。そのため、クエリとコマンドを分離すれば、複雑さが桁違いに軽減されます。大まかな数学表現を使用すると、CQRS と包括的なドメイン モデルのアプローチを比較すると、"N + N" と "N x N" ほどの違いがあります。
CQRS への着手
基本的な作成、読み取り、更新、削除 (CRUD) のシステムを、CQRS 対応のシステムに変更できます。ユーザー データを収集してさまざまな形式で表示する、標準の ASP.NET MVC Web アプリケーションがあるとします。これは多くのアプリケーションで実行される処理であり、アーキテクトはこの処理を迅速かつ効率的にビルドする方法を把握しています。CQRS を念頭において、こうした処理を書き直すことができます。必要な変更は驚くほど少なく、得られるメリットは無限に広がる可能性があります。
この標準システムは階層構造に整理されています。このシステムには、ユース ケースを編成するコントローラーから直接呼び出されるアプリケーション サービスがあります。こうしたアプリケーション サービス (ワーカー サービスとも呼ばれる) は、コントローラーとサイドバイサイドで Web サーバーに存在します。図 1 に当てはめると、このアプリケーション サービスがアプリケーション層を形成します。このアプリケーション層をプラットフォームとして、そこから残りのシステムに対してコマンドとクエリを実行します。つまり、CQRS を適用することは、2 つの異なる中間層を使用することです。1 つの中間層がシステム状態を変更するコマンドを処理します。もう 1 つの中間層がデータを取得します。図 2 は、サンプル ASP.NET MVC プロジェクトのアーキテクチャ図を示しています。
図 2 ASP.NET MVC プロジェクトの CQRS アーキテクチャ
クエリ スタックとコマンド スタックという 2 つのクラス ライブラリ プロジェクトを作成し、メインの Web サーバー プロジェクトからこの両方を参照します。
クエリ スタック
クエリ スタック クラス ライブラリは、データの取得だけを担当します。ここではプレゼンテーション層で使用されるデータにできる限り一致するデータ モデルを使用します。ここにはビジネス規則がほとんど必要ありません。通常、ビジネス規則は状態を変更するコマンドに適用されます。
DDD によって普及したドメイン モデル パターンは、基本的にはドメイン ロジックを編成する方法です。フロント エンドからクエリを行うときは、アプリケーション ロジックとユース ケースの一部のみを扱います。通常、ビジネス ロジックは、アプリケーション固有のロジックとドメインの不変ロジックを結合した表現です。永続化の形式とプレゼンテーションの形式を把握したら、後は従来のプレーンな ADO.NET/SQL クエリで行うようにデータをマッピングするだけです。
前述のように、アプリケーション層から呼び出せる任意のコードが、システムのビジネス ドメインを表します。そのため、システムのコア ロジックを表現するのは不変の API です。保護されていない API を使用して一貫性がなく、調和の取れない操作が実行されないようにすることが理想です。そこで、クエリ スタックの読み取り専用の性質を設定するために、データベースに接続するための既定の Entity Framework コンテキスト オブジェクトを囲むラッパー クラスを追加します (以下のコードを参照)。
public class Database : IDisposable
{
private readonly QueryDbContext _context = new QueryDbContext();
public IQueryable<Customer> Customers
{
get { return _context.Customers; }
}
public void Dispose()
{
_context.Dispose();
}
}
Matches クラスは、DbContext 基本クラスの DbSet<T> コレクションとして実装します。そのため、基盤となるデータベースへのフルアクセスを提供します。このクラスを使用してクエリをセットアップし、LINQ to Entities を使用して操作を更新します。
クエリ パイプラインをセットアップする基本的な手順として、データベースへのアクセスをクエリのみに許可します。これは、Matches を IQueryable<T> として公開するラッパー クラスの役割です。アプリケーション層では Database ラッパー クラスを使用して、プレゼンテーションにデータを取り込むことを目的とするクエリを実装します。
var model = new RegisterViewModel();
using (var db = new Database())
{
var list = (from m in db.Customers select m).ToList();
model.ExistingCustomers = list;
}
これで、データ ソースとプレゼンテーションが直接接続されます。実際には、表示目的でデータを読み取って書式設定するだけです。ログインや UI の制約を利用して、プレゼンテーション層の入り口で承認を行うことを想定します。ただし、これを想定しない場合は、途中に多くの層を追加して、データのコレクションの IQueryable を使用してデータの交換を可能にします。データ モデルはデータベースと同じで、永続化の際は 1 対 1 に対応します。このモデルは、階層型式ツリー (LET) と呼ばれることもあります。
ここで、2 つの点に注意します。1 つは、取り組んでいるのが通常ビジネス規則が存在しない読み取りパイプラインであることです。ここで用意するのは承認の規則とフィルターだけです。これらは、アプリケーション層のレベルで既知になります。途中でデータ転送オブジェクトを扱う必要はありません。1 つの永続化モデルと、表示用の実際のデータ コンテナーを用意します。アプリケーション サービスでは、最終的に以下のパターンになります。
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;
コード スニペットにあるすべてのデータ転送オブジェクトは、実装しているプレゼンテーションのユース ケースに固有のものです。データ転送オブジェクトは、現在ビルドしている Razor ビューに表示するものにすぎず、このクラスは避けられません。また、すべての Where 句をアドホック IQueryable 拡張メソッドに置き換えて、コード全体をドメイン固有の言語で記述されたダイアログに変換できます。
クエリ スタックについて注意すべきもう 1 つの点は、永続化に関係します。CQRS の最もシンプルな形式では、コマンド スタックとクエリ スタックが同じデータベースを共有します。このアーキテクチャによって CQRS は従来の CRUD システムと同様になります。そうすれば、ユーザーが変更を望まない場合でも簡単に調整できます。ただし、コマンド スタックとクエリ スタックがそれぞれ独自の目的に最適化されたデータベースを保持するように、バック エンドを設計することもできます。この場合は、2 つのデータベースの同期という別の問題が生じます。
コマンド スタック
CQRS の場合、コマンド スタックでは、アプリケーションの状態を変更するタスクを実行することだけを考えます。アプリケーション層は、プレゼンテーションから要求を受け取り、コマンドをパイプラインにプッシュすることで実行を調整します。「パイプラインにコマンドをプッシュする」という表現が、CQRS のさまざまな形式の原点です。
最もシンプルな場合、コマンドのプッシュは、単なるトランザクション スクリプトの呼び出しで構成されます。コマンドをプッシュすることで、タスクに必要なすべての手順を実行するプレーンなワークフローをトリガーします。以下のコードに示すように、アプリケーション層からコマンドをプッシュするのは非常に簡単です。
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();
}
}
必要に応じて、サービスと、すべてのビジネス ロジックを実装するドメイン モデルを備えた真のドメイン層に制御を明け渡すことができます。ただし、CQRS を使用するからといって、DDD や、集約オブジェクト、ファクトリ オブジェクト、値オブジェクトなどに固執する必要はありません。ドメイン モデルに複雑さを加えることなく、コマンドとクエリを分離するメリットが得られます。
高度な CQRS
CQRS の真価は、一方を最適化することでもう一方が機能しなくなるというリスクを負うことなく、コマンドとクエリのパイプラインを自由に最適化できる点にあります。CQRS の最も基本的な形式は、1 つの共有データベースを使用し、アプリケーション層からの読み取り用と書き込み用の異なるライブラリを呼び出すものです。
もっと高度な形式として、複数のデータベース、多様な永続化、クエリ目的のデータ非正規化、イベント ソースを備え、さらに、柔軟な方法でコマンドをバックエンドに渡すものが考えられます。コマンドの送信とイベントの発行にバスを使用することにより、フローチャートを管理するのと同じ方法でタスクの定義と変更を行えるため、柔軟性が増します。同時に、必要に応じてバス コンポーネントに能力や機能を追加することにより、事実上スケールアップが可能になります。
多くの開発者が CQRS を称賛していますが、コラボレーションを行う大規模アプリケーションでは適用範囲が制限される傾向にあります。CQRS はトップレベルのアーキテクチャではなく、テクノロジに依存しないアーキテクチャです。ある程度設計パターンに依存しますが、CQRS はパターンそのものです。CQRS はシンプルかつ強力で、一般的なアプリケーションに適しています。
Dino Esposito* は、『Microsoft .NET: Architecting Applications for the Enterprise』(Microsoft Press、2014 年)、および『Programming ASP.NET MVC 5』(Microsoft Press、2014 年) の共著者です。JetBrains の Microsoft .NET Framework および Android プラットフォームのテクニカル エバンジェリストでもあります。世界各国で開催される業界のイベントで頻繁に講演しており、software2cents.wordpress.com (英語) や Twitter (twitter.com/despos、英語) でソフトウェアに関するビジョンを紹介しています。*
この記事のレビューに協力してくれた技術スタッフの Jon Arne Saeteras に心より感謝いたします。