ビジネスの成長に伴い、システムは複雑さを増してきています。新しい機能の追加、ユーザー数の増加、他システムとの連携拡大など、様々な要因がシステムの複雑性を高めていきます。CQRS(Command Query Responsibility Segregation)+ES(Event Sourcing)は、複雑化するシステムを健全に成長させ続けるために有効なアーキテクチャです。このアーキテクチャを学びたい方におすすめする1冊は11月に発売となる『CQRS+ES実践ガイド イベント駆動型モデリング/システム構築手法』です。その中から、発売前に「5-3 イベントストーミングの具体的な進め方」を抜粋してお届けします。
イベントストーミングの概要
イベントストーミングはチームで行う共同作業で、そのプロセスは大きく3つのフェーズに分かれます。
ビッグピクチャーではドメインで発生するあらゆるイベントを技術的な制約を考慮せずに洗い出し、時系列に沿って配置してシステム全体の概要を把握します。ビジネスプロセスモデリングではイベントの間の因果や依存をたどり、前提条件や副作用、例外的なケースまで含めてビジネスプロセスの全体像を描き切ります。ソフトウェアデザインではドメイン駆動設計の概念を使って、集約の特定、境界づけられたコンテキストの発見、外部システムとの連携方法の設計を行い、実装可能な設計図に仕上げます。
完成した図は開発チームの共通認識となり、実装の指針にも、変更や拡張の際の影響範囲の分析にも役立ちます。
実施形態
イベントストーミングはオンラインでも対面でも実施できます。どちらの形態にするかは、参加者がふだん同じ場所で働いているか、どれくらいの人数を集めたいか、議論にどこまで臨場感を求めるかを踏まえて選びます。
対面には参加者の熱量や身振りが伝わりやすく、付箋を囲んで自然に会話が生まれるという強みがあります。オンラインには地理的な制約を受けにくく、遠隔のメンバーやクライアントも巻き込みやすい利点があります。
オンラインでの実施
もともと提唱されたイベントストーミングは対面での実施を推奨するものでした。昨今では開発者同士が日常的に同じ場所で働いていないことが多く、またクライアントもリモート会議に習熟していて、オンラインでの実施が現実的な選択肢になっています。
注意すべきは参加人数です。あまりに大人数で開催してしまうと多くの参加者は傍観者になってしまいます。会話に加われない時間が続くのは参加者のフラストレーションになります。目安は4名程度です。もし、大人数で実施する場合には班分けするとよいでしょう。
取り上げる内容については事前にユースケースを絞っておきます。少人数での実施ではそのユースケースに詳しいメンバーをそろえることになるからです。また同時編集できるオンラインホワイトボードツールを事前に用意します。
対面での実施
対面での実施では、物理的なスペースさえ確保できれば参加人数を増やしやすく、大人数でひとつのドメインを対象にさまざまな議論を行って全体像をつかむのに向いています。
対面で実施する場合でも、付箋と物理ホワイトボードを使うか、オンラインホワイトボードツールを使うかという選択肢があります。同じ部屋に集まっていても、ツールの選択次第で進行の負荷は大きく変わります。
付箋と物理ボードを扱う場合は、付箋を何度も動かす作業が煩雑になりやすいため、付箋が丸まらないようにする剥がし方[2]なども事前に伝えておきます。
オンラインホワイトボードツールを使う場合、ワークスペースはひとつにまとめ、その中で班に分かれると議論が進みやすくなります。班ごとに大きめのモニターを用意したり、プロジェクターで現在の状況を表示したりすると議論の活性化につながります。みんなで同じ視点を共有することが議論を停滞させないコツです。
ビッグピクチャー
ビッグピクチャーはプロジェクトやドメイン全体を俯瞰する最初のフェーズで、関連するすべてのイベントを書き出し、発生順に並べていきます。精度や完璧さを求めすぎず、参加者全員の知見を可能な限り引き出すことに注力し、後々重要性が判明するイベントを取りこぼさないよう量を重視します。
イベントを書き出す
ブレインストーミングがアイデアを自由に書き出すように、イベントストーミングでもまずは参加者全員で、ドメインに起こり得るイベントを思いつく限り書き出していき、この段階ではアイデアを評価しません。各イベントは「注文された」「在庫が予約された」のように具体的かつ簡潔に過去形で表現します。
理想的には開始の合図とともに参加者全員が活発にイベントを書き出していく展開が望ましいのですが、参加者の経験が少ない場合、単に「書いてください」と促すだけでは行動を引き出せないことも多々あります。そのような場合は、ファシリテーターが質問を投げかけながらイベントを引き出していきます。慣れてくると、メンバーが自然と物語を語るように付箋を書き出してくれるようになります。
イベントは過去形で中立に書く
イベントは過去形で書き、あわせて表現は中立に保ちます。たとえば「本を貸した」「本を貸し出した」という書き方には、貸し手の目線が入っています。借り手から見れば同じ出来事は「本を借りた」になり、誰の立場で書くかで同じ事実が割れてしまいます。
そこで「本が貸し出された」のように、貸し手にも借り手にも偏らない表現で書きます。特定のアクターに寄らずビジネス全体を見下ろす、言わば神様の視点で書くことで、どの参加者から見ても同じひとつの事実になります。
イベントを定義しようとしたとき、そのイベントが適切であるか迷うことがあります。ビッグピクチャーでは特段気にせず、どんどんと書き出していくことをお勧めしますが、「いつ起きたか」を示すタイムスタンプをつけられるかどうかはイベントの妥当性を判断する基準になります。
たとえば「サーバがダウンした」は「2023年7月1日午後3時にサーバがダウンした」という特定の瞬間に起きた出来事として示せます。一方、「システムが安定している」というのは継続している状態であり、いつ何が起きたかという出来事ではないため、イベントとしては適切ではありません。
また「確認した」といったクエリに相当する操作は、基本的にイベントとしては扱いません。ドメインに対して実質的な影響を及ぼすものをイベントとして扱います。ただし、監査証跡が必要な処理などの場合は、例外的にイベントとして記録することもあります。
イベントを時系列に並べる
ある程度イベントが出そろったら、それらを発生順に並べます。参加者全員で配置を進めることで、不自然な順序に気づいたり、足りないイベントを発見したりできます。
たとえば「商品が注文された」の後に「配送が行われた」というイベントがある場合、その間の「支払いが行われた」「在庫が確保された」「出荷の準備が行われた」といったイベントが不足していることに気づきます。時系列に整理することで、こうした処理の抜けも確認できます。
付箋を整理していると、同じような意味を持つイベントが見つかることもあります。「注文が受け付けられた」と「注文を受け付けた」のように、同じ出来事を指して表現だけが異なっている付箋は、対話で確認してひとつにまとめます。オンラインツールを使用している場合は重複を削除し、付箋を使用している場合は重ねて整理するか、ペンを滑らせた労力に敬意を表しつつ破棄します。
ここで並べた時系列はあくまで暫定的なものです。のちの議論で順序の見直しが必要になることも多々あります。
ハッピーパス
イベントストーミングの議論はしばしば本筋から外れて収拾がつかなくなることがあります。特に開発者は例外処理やエラーケースに関心が高く、そこで議論が停滞しがちです。肝心の部分の議論に時間を割けなくなる事態を避けるため、ハッピーパスという考え方を導入します。
ハッピーパスは正常系の処理フローを示します。たとえばECサイトでの商品注文の場合、「商品が選択された」→「カートに追加された」→「注文が確定された」→「支払いが行われた」→「商品が発送された」→「配送が行われた」というフローになります(図1)。
このハッピーパスには「在庫切れだった」「支払いが失敗した」「配送先住所が不正だった」といった例外は含まれていません。それらは無視できない検討事項ですが、まずはこの基本フローを確実に押さえます。
このハッピーパスを先に固めておくと、例外フローはそこからの分岐として検討できます。
ハッピーパスを追っている最中にも、在庫切れや支払い失敗のような例外系のイベントは自然と口に出てきます。出てきた付箋をあえて捨てる必要はありません。いま議論しないだけで、後の例外フローの検討ではそのまま材料として使えます。脇に貼っておき、正常系を並べ終えてから立ち返れば十分です。
ホットスポット
自転車置き場の屋根の色ほど熱い議題はありません[1]。誰もが意見を言える身近な話題ほど議論は活発になるものですが、議論の熱量は、今ここで決めるべきことの証明にはなりません。
[1]パーキンソンの凡俗法則(Parkinson's law of triviality)と呼ばれる寓話です。C. Northcote Parkinsonが1957年の著書で示したもので、委員会は原子力発電所の建設計画を素通りさせる一方、自転車置き場の屋根の材質や色は誰もが理解でき、誰もが発言できるため、審議に長い時間が費やされると説きます。ソフトウェア開発の界隈ではバイクシェッディングの名でも知られています。
イベントストーミングの最中にも、その場では決定しきれない事柄やイベントではないもののドメインにとって重要な知識が浮上してきます。議論の本筋からは外れがちで、かと言って捨ててしまうには惜しいものです。こうした事柄はホットスポットとして記録します。
たとえば「支払いが行われた」という付箋を貼った途端、「決済手段には何があるのか」「クレジットカードだけでいいのか、コンビニ払いにも対応するのか」といった議論が白熱する、という場面が典型です。決済手段の検討はいずれ必ず必要になりますが、その場で決めきれるものでもなく、まずはハッピーパスの完成を優先します。そこで「決済手段の検討」と書いた付箋を残して、先へ進みます。ホットスポットは付箋を斜めに配置することで、通常のイベントと視覚的に区別します(図2)。
なお、私はホットスポットを大きく2種類に分けて扱っています。後で対応すべきTODO事項と、ドメインの重要な知識です。TODOは最終的に解消を目指しますが、ドメイン知識は継続的な参照情報として維持されることもあります。
ビジネスプロセスモデリング
ビッグピクチャーで洗い出したイベント群に、コマンド、集約、外部システム、ポリシーといった要素を加え、異なるイベントをつなぐメカニズムを明らかにしていきます。
たとえばECサイトの「注文が確定された」と「支払いが行われた」という2つのイベントの間には、決済システムとの連携や在庫確認といった重要な処理が存在し、このフェーズではそうした処理の詳細や条件を明確にしていきます。
さらにリードモデルとアクターを導入すると、誰がどのような判断や操作を行うのか、どのようなルールにもとづいて処理が進むのかという観点が加わり、開発者とビジネスを結び付ける設計図へと発展していきます。
イベントとイベントをつなぐルール
イベント単体では次のイベントを引き起こしません。イベント間のつながりにはポリシーのような仲介役が必要です。
イベント同士のつながり方のルールは次の図3のとおりです。
図3を見るとわかるとおり、イベントからイベントにつなげるためのルートには大きく2つのパターンがあります。ポリシーが仲介するルートと、リードモデルとアクターが仲介するルートです。この記事では便宜的に、前者をポリシールート、後者をリードモデルルートと呼ぶことにします。
ポリシールートは「イベント→ポリシー→コマンド→集約または外部システム→イベント」と進みます。たとえば「注文が確定された」というイベントが発生したとします。「注文が確定されたら在庫を予約する」というポリシーが働き、「在庫を予約する」コマンドが発行されます。これが在庫集約で処理された結果、「在庫が予約された」イベントが発生します。
リードモデルルートは「イベント→リードモデル→アクター→コマンド→集約または外部システム→イベント」と進みます。「商品が返品された」というイベントが発生すると、それがリードモデルに反映され、カスタマーサービス担当者(アクター)がそれを確認します。担当者が「返金処理コマンド」を実行し、決済システム(外部システム)による処理の結果として「返金が完了した」イベントが発生します。
ポリシールート
ポリシールートではアクターの判断を挟まず、ポリシーが次のコマンドを発行して処理をつなぎます(図4)。コマンド・集約・外部システム・ポリシーの4要素がそれぞれどんな役割を担うかが論点になります。
ポリシー・コマンド・集約または外部システム・イベントのセットはイベントストーミングの中で頻出します。ホワイトボードツールなどを活用している場合は基本セットとして付箋テンプレートを事前に準備し、コピーして使い回せるようにしておくと、付箋を作る作業に気を取られず議論に集中できます。
コマンド
イベントの多くは誰かが何かを意図した操作をきっかけに発生します。このきっかけを表すのがコマンドです。
コマンドはイベントと対になる動作(動詞)として書きます。たとえば「注文された」というイベントがあるなら、コマンドは「注文をする」といった具合です。
しかし、必ずしもイベントとコマンドの言葉が現在形と過去形で一対一で対応するわけではありません。たとえば「クーポンを利用する」というコマンドに対して、「割引が適用された」というイベントが発生する場合があります。コマンドは何をしたいか、イベントはその結果何が起きたかを表すため、単に同じ言葉を現在形から過去形に変えた名前になるとは限りません。
集約
コマンドとイベントを直接つなぐことはできません。システム内部では、その間に集約が挟まります。集約はコマンドを受け取り、処理を実行し、その結果としてイベントを発生させます。
たとえば「注文をする」コマンドと「注文された」イベントの間には、書籍の第4章で紹介するOrder集約のように、それらを処理する「注文」集約を配置します。
標準的なイベントストーミングでは後の段階で集約の付箋を配置するようですが、私はビジネスプロセスモデリングの時点で配置してしまうことが多いです。集約を早期に配置しておくことで後の付箋の移動を最小限に抑えられるからです。
集約の命名は名詞で行いますが、このフェーズでの命名は暫定的なものです。実際このタイミングで適切な名前を決めることは難しく、私のやり方ではたいてい無名の集約のままにしておきます。これは集約を捉えることはそもそもシステム設計側の作業であり、全体のビジネスフローが見えてから集約を見直すほうが、より適切な命名ができるためです。
外部システム
外部システムは、私たちの管理範囲外でありながら、ビジネスプロセスに直接影響を与える要素です。他チームが管理するシステムやサードパーティのサービスがこれにあたります。図では外部システムにもコマンドをつなぎ、その処理結果をイベントとして描きます。
注文システムでは外部の決済事業者が提供する「決済サービス」との連携が発生します。決済サービスは「支払いを実行する」コマンドを受け付け、「支払いが行われた」イベントを発行します。他チームが管理する在庫システムや配送システムと連携する構成であれば、それらも同じように外部システムとして描きます。
なお、同じ在庫や配送でも、自分たちで実装するなら集約の付箋になり、他チームや外部事業者に任せるなら外部システムの付箋になります。どちらとして描くかを決めることは、どこまでを自分たちで作るのかを決めることと同じです。また、自分たちの持ち物であっても、今議論しているコンテキストの外にあるシステムは外部システムとして表すことがあります。
外部システムに関連するイベントの扱いには注意が必要です。記録するのは、自社システムの状態に影響を与えるものや、アクターの行動に関わるもの、ビジネス上重要な意味を持つものです。
一方で、外部システムの内部処理に関するイベントは記録しないことがほとんどです。たとえば決済サービスの「取引IDを生成した」といった中間状態は、通常記録する必要はありません。記録すべきは最終的な結果である「決済が行われた」というイベントです。
ポリシー
ポリシーはイベントとコマンドを結び付ける要素です。ポリシーは特定のイベントが発生した際に自動的に特定のコマンドが実行されるような規則を表します。注文システムの例では「支払いが行われた」イベントが発生すると「出荷を指示する」コマンドが実行される、といった規則がポリシーとして定義されます。
ポリシーは条件付きのルールも表現できます。たとえば高額の注文にだけ承認を挟みたいなら、「注文金額が10万円以上」という条件を書いたポリシーを置き、そこから「承認を要求する」コマンドへつなぎます。条件はポリシーの付箋にそのまま明記します。逆に何も書いていない空白のポリシーは、イベントを受け取ったら常に次のコマンドを発行することを表します(図5)。
複数のイベントから同じコマンドを実行したい場合もあります。たとえば「支払いが行われた」と「後払いが承認された」という異なるイベントのどちらからでも「出荷を指示する」コマンドへ進めたい場合、それぞれのイベントからポリシーを通じてコマンドへ合流させられます。
このときポリシーをひとつにまとめて、2つのイベントから矢印を集める構成にはしません。ポリシーに入る矢印は常に1本です。ポリシーは「このイベントが起きたら」という規則の単位なので、反応するイベントごとに1枚置き、合流はコマンドハンドラで受けます。
なおポリシーには名前を付けることもできますが(「支払い完了ポリシー」など)、実用上はあまり意味を持たないため、私は基本的に名前を付けずに運用しています。
リードモデルルート
ポリシールートではポリシーがイベントを受けて次のコマンドを発行する仕組みを扱いました。これに対してリードモデルルート(図6)では、人やバッチ処理といったアクターがリードモデルの情報から判断を下し、次のコマンドを発行します。
アクター
アクターはシステムに対して意思決定や行動を起こす主体です。人の場合は「購入者」「承認担当者」など、その場面での役割を示すと誰が判断や操作をするのかが明確になります。人に代わって判断や操作を行うバッチプロセスやシステムもアクターになり得ます。
イベントストーミングでは、アクターはリードモデルから情報を得て、適切なコマンドを発行します。
リードモデル
リードモデルはアクターが意思決定を行うために必要な情報をそろえる仕組みです。イベントから必要な情報を抽出し、アクターが理解しやすい表現で提示します。
リードモデルの内容はアクターごとにさまざまで、たとえば次のとおりです。
- UIに表示する情報(商品一覧、管理画面に出す一覧など)
- データベースビュー(バッチ処理用)
- 通知(メール、アラートなど)
- レポート(定期集計結果など)
- 印刷物(バーコード、ピッキングリスト、納品書など)
リードモデルはUIそのものではありません。人間がUIから受け取っているのは判断のための情報であり、図に表すべきなのもその情報です。リードモデルに特定の画面名をつけたくなったら、アクターがその画面のどの情報をもとにコマンドの実行を判断しているのかを考えてみてください。
もうひとつ、リードモデルの付箋を置くのは、アクター(人やバッチ)がそれを見て判断する場合だけです。ポリシーでつなぐ自動の連鎖では、そこで使われるリードモデル的なデータを図に描きません。ポリシーの実装が内部でリードモデルを参照する場合も、APIを呼んで情報を取得する場合も、図の上では「コマンドに必要な情報が渡されている」という整理で済ませ、付箋には出しません。図に現れるリードモデルは、アクターの目に触れる情報だけです。
どういう情報が渡されるのか書き留めておきたい場合にはホットスポットが活躍します。
アクターとリードモデルの具体例
注文システムでは、顧客(アクター)がマイページの注文履歴(リードモデル)で注文状況を確認し、「注文をキャンセルする」「配送先を変更する」といったコマンドを発行します。
リードモデルには外部システムが作成するものもあります。たとえば配送システムから送られる「出荷完了メール」などです。この場合も描き方は変わりません。外部システムが発行した「出荷完了メールが送信された」イベントから、「出荷完了メール」のリードモデルへ矢印を伸ばします。
時間をどう表すか
ビジネスルールには時間が絡むものがあります。「注文から30分経っても支払いがなければ」「開催日の前日になったら」のように、経過した時間そのものが引き金になることは珍しくありません。イベントストーミングには時間を表す専用の付箋がないため、ここまでに出てきた要素のどれかに置き換えて表します。
ポリシーはその選択肢のひとつです。「注文から30分経っても支払いがなければ」という条件をポリシーの付箋に書き、そこから「注文を取り消す」コマンドへつなぎます。金額や区分で分岐する条件と扱いは変わりません。
期限を過ぎたという事実を受けて他の関係者が行動するなら、イベントとして図に描きます。「支払いの期限が過ぎた」というイベントを立て、そこからポリシーを通じて次のコマンドへ進めます。ポリシーの条件として書くかイベントとして立てるかは、その時間の経過を関係者が事実として語るかどうかで決めます。
定期的に実行されるバッチがコマンドを発行するなら、バッチをアクターとして置きます。夜間の締め処理が「未入金の注文を取り消す」コマンドを発行する場合も、担当者がボタンを押してコマンドを発行する場合と同様に描けます。このときバッチが処理の対象を絞り込むために参照するデータも、リードモデルとして置くことが多くなります。「支払期限を過ぎた未入金の注文一覧」というリードモデルからバッチへ矢印を伸ばせば、何を見て動く処理なのかが図の上に残ります。
支払期限や締め時刻など、ビジネスルールとして定められた時間は図に明記します。外部APIの応答待ち時間やリトライ間隔も、処理の順序や実行条件を検討するうえで必要なら書き込みます。その場合は、ビジネス上の期限と実装上の設定を区別して記載します。
条件分岐の出現場所
条件分岐は、ポリシーが条件を持つ場合、集約が内部状態に応じて異なるイベントを送出する場合、アクターがリードモデルを見て判断を下す場合の3箇所に現れます。どこで表現されるかを把握しておくと、どの付箋に分岐の責任を持たせるかを迷わずに決められます。
ポリシーによる条件分岐
ポリシーには条件を記載することがあります。受け取ったイベントの情報や関連する状況をもとに、次に発行するコマンドを切り替えます。
たとえば注文システムでは、「注文が作成された」イベントに対して、ポリシーが支払い方法の条件を判定します。支払い方法がクレジットカードとして登録されているなら「カード決済を実行する」コマンドが発行されます。銀行振込として登録されているなら「振込先メールを送る」コマンドが発行されます。このように、同じイベントを受けても満たす条件によって発行されるコマンドが変わります(図7)。
集約が送出するイベントによる条件分岐
集約による条件分岐では集約の内部状態にもとづいて異なるイベントが発生します。
注文システムを例にとると、「在庫を確保する」コマンドに対して、在庫の状態によって異なるイベントが発生します。在庫が十分にある場合は「在庫が確保された」というイベントが発生し、在庫が不足している場合は「在庫が確保できなかった」というイベントが発生します。どちらのイベントも注文プロセスにとって記録する価値のある事実で、後続の処理は発生したイベントの種類に応じて分岐していきます。
図で集約から複数のイベントに矢印が伸びていても、必ずしも条件分岐を表すわけではありません。たとえば「ユーザ情報を変更する」コマンドで名前とメールアドレスを変更し、「名前が変更された」と「メールアドレスが変更された」の両方を発行する設計もあります。図ではどちらか一方が発生するのか、両方が発生するのかを区別します。
さらに言えば、イベントを送出するかどうかそのものも条件分岐の一種です。コマンドを受けても状態を変える必要がなければ、集約は何もイベントを発行しないという選択を取れます。書籍の第4章で紹介する在庫集約が、予約の記録がない注文への解放コマンドに対して何も起こさなかったのが、この設計です。
アクターの判断による条件分岐
アクターはリードモデルの情報をもとに判断してコマンドを送信します。アクター自身が条件分岐を担っているということです。注文システムを例にすると、返品要求をレビューするカスタマーサービス担当者は、管理画面に表示される返品申請情報(リードモデル)から返品理由や商品状態を確認し、「返品を承認する」あるいは「返品を却下する」というコマンドを選択します。
ひとつのアクターから複数のコマンドへ矢印を伸ばすことで、アクターが操作を選ぶ様子を表すこともできます。ただし、同じ描き方で複数のコマンドを並行して実行する場合も表せるため、この記法自体が選択を意味するわけではありません。
仕上げ
ビジネスプロセス図が整ったところで、残るはエラーケースの取り扱いと、暫定のままだった集約の命名です。
サッドパスを作る
ハッピーパスが形成できたら、そこから外れるエラーケースのパスを検討します。このようなパスはその性質上サッドパスやアンハッピーパスと呼ばれます。
サッドパスはハッピーパスの各ステップをひとつずつ辿り直すと見つけやすくなります。「注文が確定された」「在庫が確保された」「支払いが行われた」と並んだ正常系の付箋に対して、それぞれ「ここで失敗するとしたら何が起きるか」を問い直していきます。注文システムであれば、こうして付箋を辿ると次のようなサッドパスが見えてきます。
- 在庫不足による注文失敗
- クレジットカード決済の失敗
- 配送先住所の誤り検知
- 注文キャンセル時の在庫戻し失敗
ここでは後続の処理や利用者への対応に影響する失敗を洗い出します。在庫不足や支払いの拒否だけでなく、タイムアウトや通信エラー、外部APIの5xx応答なども検討の対象になります。
ただし、起こり得るエラーをすべて分岐として描くと、処理同士の関係が読み取りにくくなります。担当者が確認する、別の手段に切り替える、進めた処理を取り消すといった対応を優先して描き、技術的な原因は必要に応じて注記します。
失敗を描くときはその後の対応も含めます。たとえば決済結果を確認できない場合は注文を保留し、担当者が結果を確認してから処理を再開する手順が考えられます。リトライ回数やタイムアウトの設定も、復旧方法を検討するために必要なら図に書き込みます。
ワークショップの場で貼り出した失敗がすべてイベントストアに残るとは限りません。コマンドがビジネスルールで拒否されたとき、集約が例外を投げてイベントを発行しない選択もあるからです。分かれ目は書籍の第4章で紹介する残高不足を失敗イベントにしたときと同じで、その失敗に反応するのが呼び出し元だけなら例外で済み、サガのような後続のビジネスプロセスが受けて動くなら失敗イベントとして残します。図は発見と対話のための道具であり、実装との対応はその後に詰めるものです。
失敗への応答は巻き戻しだけではなく、やり直し、人手での解決、後で突き合わせて整合を取るといった道もあります。補償が必要になるのは、金銭の移動や在庫の確保、顧客への約束といった確定した効果があり、かつビジネス上の判断としてそれを巻き戻す道を選ぶ場合だけです。複数の集約をまたいで状態を戻すこうした処理は、書籍の第4章で導入したサガやプロセスマネージャが担う領域です。
すべてのサッドパスを同じ深さで描き込む必要はありません。発生頻度が高いケースやビジネスへの影響が大きいケースから先に肉付けし、めったに起こらない例外はホットスポットの付箋を1枚置いて後回しにします。
集約を命名する
イベントの関連付けが完了したら集約に名前を付けます。周囲にどのコマンドが入り、どのイベントが出ていくかが見えてくると、その集約がビジネス上の何を束ねているのかがわかり、ふさわしい名前を選びやすくなります。
名前の手がかりはその集約の周りに集まったコマンドとイベントにあります。「注文をする」「注文をキャンセルする」というコマンドを受け、「注文が確定された」「注文がキャンセルされた」というイベントを発行している集約であれば、それらが共通して扱っている対象は大抵の場合「注文」です。コマンドが働きかけ、イベントが報告している名詞を拾い上げると、それがそのまま集約の名前になります。注文システムの場合、コマンドとイベントから図8のような集約名が導き出されます。
図のAは注文(OrderAggregate)、Bは在庫(InventoryAggregate)、Cは支払い(PaymentAggregate)、Dは配送(ShippingAggregate)です。
これらの命名は暫定的なもので、実装フェーズで注文と注文項目を別の集約に分割するといった判断が後から発生することもあります。名前が変わるのはドメインへの理解が進んだ証なので、図とコードの双方で名前をそろえて更新していけば十分です。
ソフトウェアデザイン
ソフトウェアデザインでは、散らばっている同じ集約をひとまとまりに整理してメソッドとイベントの設計書として確定させ、その上でコンテキストの境界を見つけて独立したビジネスの文脈ごとに設計を分離します。まずは前者のステップとして、集約ごとに要素をまとめる作業から進めます。
集約をまとめる
集約の命名を終えたら同じ集約をまとめていきます。各種コマンドとイベントをひとつの集約の周りに配置していくということです。
たとえば在庫集約の周りには「在庫を予約する」「在庫を確保する」といったコマンドと、在庫数に応じた「在庫が確保された」「在庫が確保できなかった」というイベントが集まります。これがそのまま集約の設計書となり、コマンドハンドラとして何を定義し、どのイベントを送出するのかが示されます。
コンテキストの発見
集約を整理できたら、次はコンテキストの境界を見つけていきます。境界づけられたコンテキストは共通の言語と文脈のもとで、特定のドメインモデルを適用する範囲を示します。各コンテキスト間の連携はイベントを通じて行われ、たとえば注文を扱うコンテキストから「注文が確定された」イベントが発行されると、在庫を扱うコンテキストはそれを受けて在庫の確保を行います。このようにコンテキスト間の依存関係も明らかになっていきます。
注文システムでは次のようなコンテキストが見えてきます。注文管理コンテキストは注文のライフサイクル全体を管理し、在庫管理コンテキストは商品の在庫状態を扱います。決済コンテキストは支払い処理に特化し、配送コンテキストは配送に関する処理を担当します。また発送通知のような顧客とのコミュニケーションは通知コンテキストとして分離できます。
コンテキストの定義には絶対的な基準はなく、チームで合意できるグルーピングを見出します。イベントストーミング図ではこれらのコンテキストを線で区切って表現します(図9)。
残しておくべき図について
ここまでの進め方では、ビッグピクチャーの図にコマンドや集約などを加えてビジネスプロセスモデリングを進め、ソフトウェアデザインでは付箋を集約ごとに並べ直します。同じ図を更新していくため、各フェーズの図が別々に残るわけではありません。
ビジネスプロセスモデリングの図は開発者とビジネスサイドのメンバーが出来事の因果関係や処理の分岐を話し合う際に役立ちます。そのためにこの図も残しておきたい場合は、ソフトウェアデザインに進む前に図を複製しておく方法があります。一方、ソフトウェアデザインの図は集約を実装したり、コンテキストに沿ってチームの担当範囲を検討したりする際に役立ちます。
両方を更新し続けられる体制があれば、それぞれの用途に合わせて維持する価値があります。ひとつに絞るなら、私はビジネスプロセスモデリングの図を残します。ビジネスサイドとの議論に使えるうえ、この図をもとに実装することもできるからです。
今回の抜粋記事はここまでです。続きは書籍を確認いただけますと幸いです。ぜひ11月26日の発売日に書店でお手に取ってみてください。
この記事は参考になりましたか?
【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社
この記事は参考になりましたか?
この記事をシェア

