「ドメインイベント」クラスのモデリング
続けて、イベントの情報を格納する「ドメインイベント」クラスの設計方法について見ていきましょう。イベントのモデリングを行う場合、イベント名やプロパティは「イベント発生元のコンテキスト」のユビキタス言語に従って検討します。集約のコマンドから発信されるイベントの場合、実行メソッドに準じたイベント名を使います。例えば、バックログアイテムの集約でスプリントにコミットした時のイベントであれば「BacklogItemComitted(バックログアイテムコミット時)」という名前を使用します。
なお、集約のコマンドから発信する出来事ではなく、イベントそのものの業務的な意味合いが強い場合、イベントを集約としてモデリングする場合もあります。
ドメインイベントの共通項目
IDDD本では、ドメインイベントの共通インターフェースにて2つのプロパティを定義しています。下図の通り「発生タイミング(OccurredOnプロパティ)」と「発生バージョン(EventVersionプロパティ)」です。これらの共通項目に加え、イベントごとの独自のプロパティとして、イベントの発生要因を示す情報を追加します。
![イベントクラスの実装例(BacklogItemCommittedクラス)](http://cz-cdn.shoeisha.jp/static/images/article/10392/10392_03_s.png)
BacklogItemComittedクラスの場合、テナントとバックログアイテムとスプリントの識別子(ID)をプロパティとして設計します。イベントを受け取ったサブスクライバ側では識別子(ID)をキーに、変更内容を問い合わせて必要な制御を行います。
不変なドメインイベントクラス
通常、ドメインイベントクラスは不変クラスとして設計されるため、コンストラクタではその値を設定することのみ可能で、プロパティは読み取り専用となります。
イベントの一意な識別子
ドメインイベントクラスには「一意な識別子」は存在していませんが、イベントを使って外部のコンテキストと連携したり、メッセージ基盤を使ったりする場合には、一意な識別子が必要となる場合があります(一般的に、イベントの受信側は複数回イベントが送信されてくることを考慮し、「一意な識別子」を用いて重複排除を行います)。
基本的なイベント送受信方法
続けて、基本的なイベントの連携方式を見ていきましょう。イベントの連携はパブリッシャー(出版側)と、サブスクライバ(購読側)による組み合わせで構成されます。
軽量なオブザーバーを用いたイベントの送受信
IDDD本では、シンプルにイベントを送受信するには「軽量なオブザーバー」が良いとしています。オブザーバーとは、状態の変化を他のオブジェクトに通知するデザインパターンのひとつです。
![軽量なオブザーバーを用いた基本的なイベントの流れ](http://cz-cdn.shoeisha.jp/static/images/article/10392/10392_04_s.png)
軽量なオブザーバーを用いて、イベントを処理する流れは次の通りです。
- [UIやWebサービス]:アプリケーションサービスのコマンドを呼び出す。
- [アプリケーションサービス]:イベント受信時に実行するサブスクライバをパブリッシャーに登録する。
- [アプリケーションサービス]:集約のコマンドを呼び出す。
- [集約]:ドメインイベントを生成する。
- [集約]:パブリッシャーを用いて、イベントを発信する。
- [パブリッシャー]:サブスクライバに対応するイベントの種類を確認して、イベントを配信(実行)する。
- [サブスクライバ]:自分が対応するイベントが配信されてきた場合は、イベントに適した処理を実行する。
軽量なオブザーバーの特徴は、パブリッシャーとサブスクライバは同じプロセス/スレッドで動作し、イベントを発行した同じトランザクションにて処理される点です。
イベントを発信するパブリッシャー
パブリッシャーは、サブスクライバにイベント通知を送る機能を提供します。IDDDのサンプルコードではDomainEventPublisher (Java/C#)というシステム共通のサービスとして提供されています。
イベントを受信するサブスクライバ
サブスクライバは、イベント受信時に行う機能を提供します。例えば、メールを送ったり、イベントストア(後述)に情報を保存したりします。IDDDのサンプルではDomainEventSubscriber(Java/C#)というシステム共通のインターフェースを用いて、処理別にインターフェースを実装します。
基本的なイベントの送受信の流れについては以上となります。
他システム(他の境界づけられたコンテキスト)とのイベント送受信
続けて、他の境界づけられたコンテキストへ、イベントを送信する方法について見ていきましょう。リモートの境界づけられたコンテキストと連携するためには、ドメインモデルと外部システムにイベントを連携するメッセージ基盤との間で整合性を保つ必要があります。整合性の保ち方として主に3つの方法を紹介しています。
No. | 概要 | 説明 | メリット | デメリット |
---|---|---|---|---|
1 | 共有データ領域 | ドメインモデルとメッセージ基盤で同じデータ領域を使う | ドメインモデルの変更とメッセージ基盤が同じトランザクションのため性能が良い | メッセージ基盤のストレージをドメインと同じところに置くことが難しい |
2 | 分散トランザクション | ドメインモデルとメッセージ基盤が別のデータ領域を使うが整合性を保つために2フェーズコミットを使う | ドメインモデルとメッセージ基盤でデータ領域を分離できる | 分散トランザクションの仕組みが複雑、未対応のプロダクトも多い |
3 | イベントストア | ドメインモデルとイベント用のストレージを同じデータ領域に用意する | ドメインモデルが直接メッセージ基盤を使わないためシンプル | イベントストアのイベントをメッセージ基盤に転送する仕組みが必要 |
本稿では、IDDDとして特徴的な3番目のイベントストア方式を中心に紹介していきます。また、他の境界づけられたコンテキストと連携する仕組みとして、RESTで通知する方法と、メッセージングミドルウェア(RabbitMQ)で通知する方法を紹介します。
結果整合性と遅延の許容範囲
なお、複数の境界づけられたコンテキストに連携する時には結果整合性が重要となります。非同期による遅延の許容範囲がどの程度であるかは、ドメインエキスパートに聞いて確認します。リアルタイム性が求められた場合は、現システムが導入される前は、どの程度の遅延が許容されていたのか確認してみるといいでしょう。