2015 年 8 月

Volume 30 Number 8

Cutting Edge - CQRS とイベント: 強力なコンビ

Dino Esposito | 2015 年 8 月

Dino Esposito最新ソフトウェアの多くの側面がそうであるように、まったく新しいものなどありません。新しく魅力的な専門用語に改称されているだけのことが多く、コマンド クエリ責務分離 (CQRS: Command Query Responsibility Segregation) はその好例です。また、ビジネス ドメインの目に見える変化を表すドメイン イベントも同じです。

1980 年代後半、Bertrand Meyer はプログラミング言語 Eiffel を定義し、現在 CQRS と呼ばれているものの土台となる原則を形作りました。ソフトウェアでは、すべての基本操作はコマンドかクエリになります。両方になることは決してありません。コマンドはシステム状態を変更することが想定され、クエリはシステム状態をまったく変更しないで報告することが想定されます。

つまり、質問することで、答えを変えてはいけません。これを、コマンド クエリ分離 (CQS: Command Query Separation) の原則と呼ぶこともあります。この原則は、コマンドとクエリには当てはまりますが、ドメイン イベントについてはどうでしょう。

ビジネス アプリケーションにおけるイベント

アーキテクトはソフトウェア エンジニアリング初期の頃から、基幹業務アプリケーションがビジネスの変化に耐え、その変化を追跡できるように設計してきました。また、ビジネス インテリジェンスや統計分析をサポートするために、一連の操作をある程度反復できるようにしてきました。すぐには「ドメイン イベント」や「イベント ソーシング」のようなすてきな新語が作られたわけではありませんが、ドメイン イベントの考え方はビジネス システム分野で長年にわたり広範囲に使用され続けています。特に会計、金融、銀行のシステムには欠かせない考え方です。

その後、ドメイン駆動型設計 (DDD) という革命が起き、この "新しく" 普遍的なソフトウェア モデルに迷い込んだアーキテクトたちは、ソフトウェア アーキテクチャという展望をいくぶん失ったように思います。開発者が主に注目するのはシステムを横方向に区切る複数の層で、コマンド スタックやクエリ スタックなど、システムを縦方向に見る考え方は軽視されるようになります。

個人的見解ですが、Bertrand Meyer によって確立された CQS 原則と現在の CQRS 原則の違いはすべて、原則を当てはめる場所にあると考えます。CQS はどちらかと言うとソフトウェア開発の普遍的原則で、システム アーキテクチャに当てはめるのが CQRS です。アーキテクチャ レベルに CQS を適用すると、CQRS になります。

入門レベルの CQRS については「一般的なアプリケーション向けの CQRS」(msdn.microsoft.com/magazine/mt147237) で取り上げました。「CQRS とメッセージベースのアプリケーション」(msdn.microsoft.com/magazine/mt238399) では、少し細部に踏み込みました。

2 つのコラムでは、プレゼンテーションとビジネス ロジック間で、アプリケーション層を経由して交換されるメッセージに基づくコマンド スタックについて説明しました。また、製品の注文やキャンセルなど関連ビジネス項目をログ記録するために、アドホックなストアに格納できるビジネス イベントについても少し触れました。

注目したのは、コマンドによって引き起こされるビジネス ワークフローを実装するためにメッセージを使用することでした。このような状況では、イベントは単なるハンドラー間の通知に過ぎず、ハンドラーが対応しなくてはならない最新イベントの発生を知らせるだけでした。これは、ソフトウェア アーキテクトの考え方を「永続化するためのモデル」から「記録するためのイベント」に変えることを目的とした、長きにわたる進化の第一歩です。個人的には CQRS について、システム アーキテクチャの細部まで影響を与えるであろう変化の始まりだと思っています。

ニュートンのリンゴ

リンゴが木から落ちるのを見て、万有引力の法則を思いついたというアイザック ニュートンの話は有名です。これは単なる言い伝えで真実ではないと思われますが、好奇心をそそる出来事をきっかけとして発見された事実はたくさんあります。最近、CQRS とイベントについて深く考えさせられる、ニュートンのような経験をしました。それは、顧客が 5 年にわたって使用しているシステムを再構築するために、事前分析を行っていたときのことです。

顧客のシステムは、ASP.NET MVC 2 Web サイトで、バックエンドにプレーンな SQL Server データベースを使用していました。データ アクセス層は、リレーショナル データベースにまとめられ、Entity Framework 経由して利用します。推論されるエンティティ クラスは、Microsoft .NET Framework の部分クラス メカニズムを使用するビジネス固有の追加のメソッドで拡張されています。エンティティをまたがる動作はドメイン サービスに委任されます。これはドメイン モデル パターンの参照実装とは言えませんが、あちこちに DDD のかけらが見受けられます。基本的には、バックエンドとシステムの残りの部分の間のバッファーとしていくらかのビジネス ロジックがある、プレーンな作成、読み取り、更新、削除 (CRUD) システムと考えられます。

ソフトウェアにおける CRUD システムは、多数のデータベース テーブルを中心に構築されるアプリケーションです。データベースを囲む層は、データベースに対する、中核となる CRUD 操作を検証して、その土台を構築します。顧客のシステムは、このようになっていました。

CRUD システムについて、あまり気付かれず、十分注意が払われないのが、データベースをスナップショットとして使用するという一面です。CRUD システムで利用するデータベースは常に、前回正常起動時のシステム状態を格納します。これで十分なときもあれば、そうでないときもあります。

ニュートンのようにひらめいたのは、スナップショット データベースのアプローチは最初のバージョンに適しているということです。バージョンが上がると、顧客はさらに多くのことを求めます。履歴を追跡し、さまざまな状況下で一連の手順を繰り返して結果を判断できるようになることを求めます。この場合、ストレージの唯一の (主な) 形式がストレージのスナップショット データベースでは、適切とは言えなくなります。

サンプル予約アプリケーション

会議室を予約するアプリケーションがあるとします。どこかの時点で、プレゼンテーション層は図 1 のような画面を表示します。

各部屋のタイムシートのモックアップ
図 1 各部屋のタイムシートのモックアップ

各部屋にはそれぞれ、予約に関する独自のビジネス ルールがあるとします。たとえば、ユーザーは「White Room」を 8 ~ 10 時と、13 時以降に 1 時間刻みで予約できます。「Blue Room」は、9 時 30 分以降 30 分刻みで予約できます。また、「Green Room」は 9 時から 1 時間刻みで予約できます。このような詳細が保存され、既存の予約と照合されます。グリッドを生成して、既に埋まっている時間枠をグレー表示し、それ以外をクリック可能にすることができます。

部屋の空き状況を管理するルールは不変ではなく、変化する可能性があります。これは、それほど問題ではありません。もしもルールが変わったら、管理者がルールを適宜編集できるようにします。最新のシステム状態を表示するのに図 1 のモックアップを使用している場合、このアプローチは適切に機能します。

ところが、顧客が現在のグリッドと 3 週間前のグリッドを行き来できるようにすることを求めてきたら、頭を抱えることになります。正確に言うと、過去 3 週間のうちに予約のビジネス ルールになんらかの変更が発生した場合です。部屋ごとに現在の時間枠と時刻は把握していても、 3 週間前の状態は失われています。

顧客がこのように要求してきたとき、不足している情報をテーブルに持ってくれば済む話だと考えました。実行したのは、いくつかのルールを保存して、必要に応じてそれを再度読み込むことだけでした。簡単に聞こえるかもしれませんが、このような少量の不足情報に適切に対処するためには、アーキテクチャを大きく変更しなくてはなりません。それはまるで、水面上がスナップショット データベースで、水面下がイベントベースのデータ表現である氷山の一角を操作するようなものです。

イベント ソーシングとは、イベントベースのデータ表現のことです。ここでは詳しく説明しません。ただし、次回のコラムで取り上げる予定です。ここからは、データベースのスナップショットとビジネス イベントのログを結び付ける、混在アプローチについて説明します。これはおそらく、既存の古典的なアーキテクチャを、未来のアーキテクチャに変換する優れた第一歩になります。どれほど優れたテクノロジ、フレームワーク、リリース、およびランタイムが登場しようとも、今後数年で最も重要なのは主にアーキテクチャ周りの進化です。つまり、主要データ ソースはデータのスナップショットではなく、イベントベースのデータになります (図 2 参照)。

水面下のイベントの氷山
図 2 水面下のイベントの氷山

データのスナップショット以上のもの

ここ数十年間、ほとんどの開発者はシステムの最新状態のみを考慮してシステムを構築してきました。このシナリオでは、データベースがエンティティのスナップショットを格納 (DDD の用語で言うなら集約) していれば問題ありませんでした。ビジネス イベントが発生してもログに記録する必要性を感じなければ、ログを記録することはないでしょう。

しかし顧客が、アプリケーションの時計を特定の時間に戻さなくてはならない機能を要求したとき、開発者は困り果ててしまいます。唯一の解決策は、ビジネス イベントを保存するようシステムを構築しなおすことです。たとえば、図 3 のようなテーブルがあるとします。

部屋の予約ルールを格納するサンプル テーブル
図 3 部屋の予約ルールを格納するサンプル テーブル

このテーブルを見ると、3 月 1 日から 6 月 1 日までは、さまざまなルールに従って「部屋 1」を予約できることがわかります。また、参照している日付によっては図 1 のグリッドを設計しなおす必要があることもわかります。

RoomRules テーブルのレコードは、予約ルールを変更するために発生したイベントを示しているという点で、論理的にはビジネス イベントに相当します。しかし、実装の点から考えると、これは追加テーブルに含まれるプレーンなレコードで、図 1 のグリッドを設定するためには追加のクエリ (1 部屋につき 1 つ) が多数必要になります。

新しい予約ルールが決まると、単純に、テーブルに新しいレコードが必要になります。ただし、賢明な開発者であれば、既になんらかの種類のルール テーブルをおそらく用意していて、1 部屋につき 1 行を提供するだけです。今回の場合、図 3 の ValidSince という追加列が、唯一の差異のように思われます。

従来のスナップショットの記憶域の観点から言うと、追加のテーブルで追加レコードを多数定義するために "ビジネス イベント" という言葉を使うのはやり過ぎのように思えます。それでもこれは確かに、開発者がスナップショットからイベント ソーシングに頭を切り替えようとするときに、過渡的に考えることではあります。

もっと具体的な言葉で言うと、部屋の予約ルールはイベントです。また、ルールのログ記録は、システム履歴の追跡に該当します。そして、ある部屋に適用するルールを取得することは、システムが開始してから特定の時点に到達するときまでに発生したその部屋のイベントを "再生" することです。現在の状態をどこかに保存していなくても、すべてのイベントのログは記録しています。システム状態を取得するには、イベントを再生して、Room エンティティの有効なインスタンスを構築します。

奇妙に思われるかもしれませんが、銀行取引アプリケーションでは、現在の "残高" をこのようにして計算しています。残高はプレーンな読み取りから得られるものではなく、むしろ、特定の日付に永続化されたものを、その後に発生するすべてのイベント内で再生するスナップショットです。

イベントと CQRS のコンビ

イベントは、想像を絶するほどにソフトウェアを強化します。プレーンな CRUD システムすらも、顧客が簡単な形式のビジネス インテリジェンスと統計分析を要求すれば、即座に操作されます。イベントは不変なので、イベント データベースをソフトウェアの新しいインスタンスにレプリケートするのは簡単で、これはスケーラビリティにとって重要です。同時に、コマンドとクエリを分離すると (つまり、コマンド処理とクエリ実行に別々のスタックを使用すると)、コマンド スタック内のイベントに集中でき、スナップショット テーブルがクエリ スタックで簡単に読み取られるようになります。

パフォーマンスについての懸念がある場合は、コマンドを実行するときにイベントを保存して、クエリに必要なすべてのデータ プロジェクションを更新します。たとえば、新しい予約ルールを追加したときから、そのルールが既定になります。また、イベントを再生しなくてもすばやくクエリできる、現在の設定のテーブルを追加で更新することもできます。その後、必要に応じてイベントを再生します。

まとめ

イベントがあれば、すべてを手にすることが可能です。ビジネスを継続しながらも、より強力なアーキテクチャの種類について知ることができます。データをイベントとして格納すると、データ抽象化レベルが 1 つ下がります。つまり、それぞれ同量の低レベルのデータを使用して、プレーンなクエリ、ビジネス インテリジェンス、what-if 分析、統計、使用分析など、考え得るすべてのビジネス目的でいくつでもプロジェクションとスナップショットを構築することができます。


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 に心より感謝いたします。