ヘキサゴナルでのサービス指向のアプローチ
SaaSOvationチームは、レガシーなコラボレーションツールからの移行用サービスを作成するにあたり、ESB Muleという製品を導入しました。ESB(エンタープライズサービスバス)とは、コードを書かずにメッセージング基盤を提供するミドルウェア/インフラ製品で、SOAの機能も提供されています。
SOA(Service Oriented Architecture:サービス指向アーキテクチャ)の導入
SOA(Service Oriented Architecture)とはサービス指向アーキテクチャのことで、ソフトウェアをサービスとして連携させ、システム全体を構築していきます。ヘキサゴナルアーキテクチャのサービス設計において、SOAの設計指針を意識することは有益です。
サービス設計の指針としては、ビジネス戦略を示す「ビジネスサービス」とREST/SOAP/メッセージ型といった「技術的サービス」の両面を意識します。さらにDDDでは「ユビキタス言語」や「境界づけられたコンテキスト」が不自然に分断されていないかをチェックすることで、より良いサービスを公開できます。
ヘキサゴナルで使用されるREST
DDDでWebサービスを構築する時によく利用される技術がRESTです。SaaSOvationチームは、モバイル対応、認証管理、BIレポートの対応において、RESTを採用しました。
RESTfulなシステムとは
REST(Representational State Transfer)は、Roy Fielding氏が書いた博士論文にて登場したアーキテクチャスタイルです。このRESTに従ったサービスを「RESTful」と呼びます。RESTという言葉は普及していますが、定義が曖昧なことも多いので簡単に特徴を整理します。
(1)リソースを一意なURIで識別可能
RESTでは「URI(Uniform Resource Identifier)」 による一意なアドレスを持ち、提供する「リソース」を識別できます。例えば、提供するサービスの顧客/プロダクト/検索結果といった情報リソースを一意なURIにて識別できることを表します
(2)ステートレスな通信
RESTではクライアントとサーバー間の通信はステートレスに行われます。RESTfulなHTTPサーバーは前回のリクエストの状態などを記憶しないため、各HTTPメッセージには、そのリクエストを処理するために必要な情報が全て含まれています。
(3)GET/POST等の命令指針に基づいたリソース操作
RESTでは「GET(取得)」「POST(登録)」「PUT(登録または更新)」「DELETE(削除)」等のHTTPメソッドを用いてリソースを操作できます。これらの命令には標準的な振る舞いが決まっています。例えば、GETはデータを読み込み、キャッシュ可能な振る舞いを提供します。厳密には、変更を伴わない「安全」な操作で、同じ処理を何回呼び出しても結果が変わらず問題も発生しない「冪等(べきとう)」な操作と言われます。
これらの体系化された命令を使うことにより、URLの中に/GetFooや/DeleteBarといった動詞を含める必要が無くなり、名詞主体のわかりやすいURLを構成できます。
(4)関連リソースへのナビゲーションを扱う「ハイパーメディア」
RESTでは、ハイパーメディアを用いて、レスポンスの中に他のリソース情報を埋め込むことができます。Fielding氏の論文では、このことをHATEOAS(Hypermdia as the Engine of Application State)と呼んでいます。
DDDでRESTの使用
RESTは理解が容易で、疎結合で利用がしやすい仕組みのためスケーラブルなサービスの提供にも適しています。そのため、DDDのヘキサゴナルアーキテクチャとの相性も良いとされています。
なお、RESTで公開するインターフェイスは、コアドメインをそのまま公開するのではなく、公開用の独自モデルを設計するか、ical(一般的なカレンダー形式)のような汎用的なメディアタイプを使用することが推奨されています。
コマンドクエリ責務分離(CQRS:Command Query Responsibility Segregation)
SaaSOvationチームは、ユーザー別に複雑化してきたダッシュボード画面の通知情報を管理するため、CQRSアーキテクチャーを採用しました。CQRS(Command Query Responsibility Segregation)とは、その名前の通りコマンドとクエリを分離することで、更新と取得それぞれに特化したモデルと処理を実装します。
コマンドクエリ責務分離とは
CQRSパターンの元になっている原則は、コマンドとクエリを分解するアーキテクチャパターンであるCQS(Command Query Separation)です。これはBertrand Meyer氏が書籍「Object-Oriented Software Construction」にて提唱しました。実装レベルに要約すると以下の2つに分けられます。
- コマンド(ライト):オブジェクトの状態を変更するメソッドは値を戻してはいけない。戻り値の型はVoidである。
- クエリ(リード):メソッドが型や値を戻す場合、オブジェクトの状態を変更してはいけない。
従来のプログラミングでは、更新と取得を同じメソッドを記述していたかもしれませんが、CQRSでは「更新メソッド」と「結果取得メソッド」として明確に2つに分離します。
DDDにおけるCQRSでの流れ
それでは図に従い、CQRSの流れを見ていきましょう。
(1)コマンドの処理(コマンドプロセッサ/コマンドハンドラ)
サーバー側にて更新処理を開始します。この処理部分は、コマンドプロセッサ(コマンドハンドラ)と呼ばれます。このコマンドプロセッサの構築方法については、次の3方式があります。
同期処理の場合は「(a)分類方式(アプリケーションサービスに複数のコマンドメソッドを追加)」か「(b)専用方式(単一メソッドを持つ単一クラスを作成)」を選択します。前者のメリットは開発が容易で、後者のメリットはコマンドごとの責務が明確になります。非同期処理の場合は「(c)メッセージング方式」を用います。この方式では「専用方式」のコマンドクラスを非同期のメッセージとして送信します。複雑になるためスケーラビリティが必要な場合のみ選択するようにします。
どの方式にせよ、コマンドプロセッサは、コマンドモデルである集約のインスタンスを作成/取得し、更新用のメソッドを実行します(集約については別途10章で紹介します)。
(2)コマンドモデル(ライトモデル)
通常、集約がコマンドモデルとなります。コマンドモデルの更新系メソッドが呼び出される場合、最後に「ドメインイベント」が発行されます。例えば「会員が登録された時」というようなイベントを発行します。
(3)コマンドモデル用データストア
コマンドモデルの更新結果が保存されます。データの更新内容がレポジトリ経由で格納されます。
(4)コマンド処理を実行(イベントのサブスクライバ)
サブスクライバ(購読者)が発行されたドメインイベントを受信します。そして、受け取ったイベントの内容に従ってクエリモデル(クエリ用のデータ)の更新を行います。
(5)クエリモデル用データストア
クエリモデル用データストアには、描画用のデータが格納されています。データの格納場所の決まりはありませんが、データベースのテーブルが一般的です。高速化のため事前にテーブルをジョインして非正規化した「マテリアライズドビュー」を使用する場合もあります。マテリアライズドビューはデータベースの標準機能を使用する場合もありますし、プログラムで事前にデータを構築する場合もあります。また、性能の観点から複数台のレプリカを持つ場合もあります。
(6)クエリモデル(リードモデル)
クエリモデルは、画面表示や印刷用のための非正規化されたデータモデルです。
(7)クエリ処理(クエリプロセッサ)
データベースの結果セットをそのままかJSON/XMLで戻したり、DTO(データトランスファーオブジェクト)という描画モデルに詰め替えたりします。これらの方式については、プロジェクトにおいて最適な方法を選択します。
上記の流れでCQRSによるデータの更新と描画が可能となります。
CQRSでの同期/非同期の採用指針
CQRSで同期的に処理するか、非同期に処理するかは機能要件次第です。同じDB内に「コマンドモデル用データストア」と「クエリモデル用データストア」を用意し、同一のトランザクションを用いれば同期的に処理することができます。同一トランザクションの場合、一貫性が保たれるメリットがある反面、処理時間がかかるデメリットがあります。これに対して非同期処理を用いる場合は、いったんレスポンスを返せるため高速になりますが、一貫性が保たれないため「結果整合性」を保つ必要があります。
CQRSと結果整合性
「結果整合性」とは「結果として一貫性が保たれることが保証されていれば問題ない」という考え方です。標準的なRDBMSアプリケーションでは、ひとつの処理でビジネスルールを正しく保つ「トランザクション整合性」が一般的(例:銀行口座の入出金では必須)です。しかし、DDDではドメインエキスパートの観点から、最終的に一貫性が保たれれば多少のタイムラグが合っても問題がないと判断される場合、「結果整合性」を用いることで複雑性を排除することができます。
非同期のCRQSでは結果整合性は保たれますが、クエリモデルの更新までに多少のタイムラグが発生します。これを補完するためアプリケーション側で、更新依頼したユーザーに対して入力データを使った疑似的な画面更新を行ったり、データの最終更新時刻を表示したりといったUI対策を実施する場合があります。さらにリアルタイムな更新が必要な場合は、サーバー側からプッシュする技術(Commet/WebSocket/Server Sent Events等)の採用を検討する場合もあります。
以上、CQRSをDDDと組み合わせる場合の概要について紹介しました。