マイクロサービスとメッセージングのなぜ [希望編]

レッドハットでインテグレーションのためのミドルウェアのテクニカルサポートを担当している山下です。以前、SAGAやEventStormingについて記述すると宣言していたのですが、実際のところ私が書くよりもよっぽど良い日本語の書籍や記事がでていて、もう書く必要もないと思っていたのですが、今回機会をいただいたので約4年ぶりに”マイクロサービスとメッセージングのなぜ"の希望編を書くことになりました。今回の記事ではSAGAやEventStormingの詳細は書かないのですが、私がイベントやメッセージングが必要と考えるに至った危機感や希望を共有します。そうした意味ではむしろ原点ともいえる内容になっています。なお今回記事にはとりわけ個人的な経験や意見が多く含まれますので、事前に異論は認めることにします。

終わりのなく続く名詞集めとモノリシックなモデル

リレーショナルデータベース中心アーキテクチャによってプロジェクトを進めてきた方は私を含め多くいらっしゃるのではないでしょうか。様々な方法がありますが、それは例えば、ユースケースから名詞を拾い集め、データ中心によるモデル、とりわけデータ構造を定義していく方法があります。

要件やビジネスプロセスを分析して、ユースケースの詳細を記述したユースケース記述では、システムがどのように使用されるかをユーザーや外部システムの視点から記述します。そこでは画面UIなどからシステムへのクエリやコマンドで利用される具体的な名詞(≒ データ)が記述されています。そしてそれらの名詞を洗い出してデータの正規化を行ない、そうしてできたデータ構造はER図やメソッドが省略されたクラス図に落とし込んでシステムの骨格を形づくっていきます。データ構造が決まれば、それらを利用する処理を配置するクラスは自ずと明らかになるので、プロラミングではデータ構造に肉付けするように開発を進めていくことができるようになるのです。

おそらくこれは現在でも最も実践的な方法の1つであり、適したプロジェクトも数多くあります。実際私もこうした方法を実践してきました。この方法に一概に問題があるというつもりは全くないのですが、しかし限界もあります。

変更コストは高い、事前に予期して完璧なモデルを作成する

こうしたデータ構造のモデルは画面などと比べれば安定しているともいわれたりしますが、とはいえシステム要件が追加されることで変わっていくことはよくあり、実際のところ安定しているなどとはとてもいえないものです。また些細なように見える追加要件がドラスティックにデータ構造のモデルを変えて、そこから各種の設計書やUIに変更が波及し続ける開発を自分を含め多くの方が経験しているでしょう。データ構造の変更はそれに依存した多くの実装への修正を伴うために、大きな手戻りやコストを伴います。ですから、どれだけ要件を予め洗い出すことができるのか、また顧客のニーズを予期して、できるだけ事前にモデルに取り入れることに努めて、実装に進む前により完璧なモデルを追求することになります。

境界なくフラットに広がり続けるデータ構造のモデル

より完璧なモデルを追求する中で、様々な要件や一見して想定しえないような例外的な要件であったとしてもモデルはそれを飲み込み続けなければなりません。なぜなら想定されなかった要件や事実をモデルは受け入れず、その事実自体がむしろ存在しなかったように否定されてしまうからです。”一度の注文でも発送は数回に分割することが時々ある”、“あとから当時の状況がわかるように履歴を作りたい”、”棚下しの中でで発送したはずの商品がバーコードで検出された”、どれも胃が痛くなってきます。こうして、様々なドメインを巻き込みながら、履歴/事実、状態、例外状態、あらゆるコマンドやクエリに対応して、境界なくフラットにデータ構造のモデルは広がり続けます。

ブロックされる開発

広がり続けるモデルの一方で、要件をできるだけヒアリングして網羅しモデルやデータ構造が十分に納得できるまで、開発が進められないといったことが起こります。後戻りのコストは高くつくために、モデルがより完璧でなければならないのです。とはいえどこかで区切りをつけて開発を開始しなければなりません。逆算してプロジェクトのデッドラインが許すまでの分析を続けることになります。そして開発が始まった後には、データ構造のモデルが変化するような要件が発覚しないよう、不安を抱えながら祈り続けるのです。

モノリシックなシステムに突き進む

名詞集めは十分に実践的ですが、様々な要件のクエリやコマンドから名詞を洗い出し、それらすべての整合性を保ったデータ構造を設計し続けなければなりません。新たな要件を満たすように少しづつ巨大化していくデータ構造のモデルは、フラットに広がり続けます。モデルは想定を超えて大きくなり、その大きな役割をますますリレーショナルデータベースに頼るようになります。実際のところ様々な要件を満たす完璧なモデルには近づくことさえ難しいことも多くの方はご存知でしょう、新しい要件の統合はより難しさ増していきます。しかし整理したり分割しようとしても、データベース中心アーキテクチャで確立されたモデルを分割することは、ソフトウェア開発における最も高価なリファクタリングの1つです。後戻りもできないままに、重箱の隅をつつくように名詞を集め続け、より完璧なモデルを追求しながらモノリシックなモデル、そしてモノリシックなシステムへと突き進み続けることになります。

イベントを集めるEventStorming

上記のような名詞を集める方法に対して、イベントを集めるEventStormingという方法があります。これはポストイットを使ったコラボレーションワークショップで、イベントを中心としたアプローチが特徴です。EventStormingの目的には、ドメインを理解するための学習やチームワーク、問題発見や解決策の創出、ビジネスプロセスの可視化や、境界づけられたコンテキストや集約の識別などがあり、それらの目的に応じて様々なテクニックや手法を調整して利用します。

ここではEventStormingの詳細は説明しませんが、大まかには以下のような手順で進みます。

1. イベント(オレンジ色のポストイット)を洗い出す

まずビジネスプロセスに関連するイベントを、大まかな時系列に沿って洗い出していきます。イベントは通常「~した」という過去形あるいは過去分詞形の形式で記述され、システムに関連した事実を表します。イベントは、ユーザーの操作、外部システム、時間、他のイベントなどから発生しますが、しかしとりわけこの時点では特定の発生元に囚われることなく当該のドメイン内で発生している事実を洗い出していきます。またイベント名に過去の時制の動詞を使用して、状態遷移に焦点を当てることで、その推論からドメイン全体の探索へ繋げていきます。

2. コマンド(青色のポストイット)の特定

各イベントをトリガーするコマンドを特定します。コマンドはアクターの判断の結果として行われるシステムへの指示で「~する」という形式で記述されます。なおアクターは黄色のポストイットで表します。

3. リードモデル(緑色のポストイット)の追加

アクターがコマンドを実施する際の判断に必要な情報をリードモデルとして追加します。リードモデルは、データの表示や分析に特化したモデルでクエリ操作のために利用されます。

4. ポリシー(ライラック色のポストイット)の追加

何らかのイベントが発生した時にコマンドを発行するルールとしてポリシーを追加します。これらは「〜(何らかのイベント) するときはいつでも」で始まることも多く、システムが自動的に行うことや、人が記憶しておくべきことなどを表します。

5. 集約(大きな薄黄色のポストイット)の識別

関連するイベントやコマンド群、ビジネスルールやトランザクションによるデータ整合性を維持するための集約を識別します。集約はドメインモデルの一部で、データと処理をカプセル化した責務を持つ単位です。集約は、イベントソーシングの文脈ではデータの書き込みに特化したライトモデルに対応し、コマンドに対して異なる応答をするので、小さなステートマシンのようにみえることもあります。

6. 境界づけられたコンテキストを定義する

言葉や意味の一貫性の程度が高いグループ、同時に変更される可能性が高い機能のまとまりから、境界づけられたコンテキストを定義します。モデルの一貫性は境界づけられたコンテキスト内のでのみ達成でき、そのモデルは限界を超えて大きくすることはできません。意味の一貫性の程度が高い複数の比較的小さなモデルを目指します。

集約の定義は意図的に延期されている

EventStormingでははじめにイベントを集めていく一方で、集約の識別は手順の終わりの方になるまで意図的に延期されます。ビジネスプロセスやシステムの振る舞いを十分に理解した後に、コマンドやイベント、ポリシーなどを頼りにして、責務に基づいた精度の高い集約のモデリングが可能になります。私たちはモデルの内部構造について画面上で見たいものを重ね合わせ、名詞を集めてくる習慣によって、責務ではなく背後にあるデータの正規化から暗に集約を見つけようとしてしまうことがあります。しかしそうすると同じ識別子(ID)に紐づくデータとそれに関連するイベントやコマンドまでも引きづられて塊になり、集約が肥大化して分割が難しくなるので注意してください。

イベントを集めてもっと小さく柔軟で進化するモデルへ

リードモデル、集約(ライトモデル)、イベント

EventStormingではリードモデルと集約(ライトモデル)の2つの役割のモデルがあります。また事実や履歴についてはイベントに任せることができるでしょう。さらには境界づけられたコンテキストごとに必要な機能や責務によってもモデルを分割できる柔軟性があります。

これらは基礎としてイベントが存在していることによって実現されます。マイクロサービスのような小さく独立したサービス間での連携が行えるほか、データ転送によって統合された表示用のリードモデルを実現することができます。またシステムの状態変化をイベントとして蓄積(イベントソーシング)すれば、イベント自体が事実や履歴となり、またモデルの変更があっても過去のイベントの蓄積から再評価して新しいモデルへと柔軟に進化させることができます。さらには過度な要件をモデルに取り込まないという選択ができることもあります。例えば本来想定しえないような稀で例外的な要件は、イベントに保存しておき、集約(ライトモデル)には取り込まずに例外のレポートを出力させるに留めるといった方法もありえます。

境界づけられたコンテキストによる分割

また境界づけられたコンテキストにより、同じ識別子(ID)を共有するエンティディであったとしても、それぞれに特化したモデルに分割できることもあります。例えば一口に"ユーザ"という同じ識別子(ID)を共有するエンティティであったとしても、境界づけられたコンテキストが異なれば、受注では購入者、請求では請求先、出荷では配送先、CRMでは顧客、システム上ではログインユーザといった具合に独自のモデルによってデータや機能が異なることがありえます。

上記は単純な例ではありますが、これらは同じユーザIDを持ちながらも、境界づけられたコンテキストごとに異なるデータや振る舞いを持った集約として分割されました。ところで集約はトランザクションによるデータ整合性を維持する単位でもあるため、更新時の排他制御やロックも考慮することになります。一般的にはレコード単位を集約ルートのインスタンスして、そこにロックをかけてデータ整合性を確保することが多いでしょう。しかし例えばログインユーザの場合にはログインユーザ名やメールアドレスが全体の中でユニークでなければならないといった要件を実現するために縦にカラム一式を排他制御するするシングルトンの集約にすることも考えられるかもしれません。レコードによる主キーや正規化にばかりに気を取られずに、集約の振る舞いに注目すれば、もっと自由な視点でモデルを分割できる可能性が広がります。

イベントを導入することで、システム内のサービスは小さな独立したモデルに分割され、それぞれが特定イベントに反応して動作するようになります。そして各モデルが新しい要件に柔軟に対応でき、全体のシステムを複雑化することなく進化し続けることができます。

まとめ

今回は、ソフトウェア開発における名詞を集めてデータ構造を定義していくアプローチと、EventStormingによるイベント集めのアプローチについて説明しました。名詞を集めてデータ構造を定義していく方法は十分に実践的で、それが適したプロジェクトも数多くあります。しかしあらゆる要件を満たすように少しづつ巨大化して、柔軟性に欠けたモノリシックなシステムへと突き進み続けることになる可能性があります。

一方、EventStormingによるイベント集めのアプローチでは、イベント、コマンド、リードモデル、ポリシー、集約などを用いて、ビジネスプロセスを理解し、より柔軟で適応性の高いモデルを構築します。またリードモデルや集約(ライトモデル)、イベントなど異なる役割を持ったモデルに分割され、さらには境界づけられたコンテキストごとに必要な機能や責務によってもモデルを分割できる柔軟性があります。こうした分割により新しい要件に対して柔軟に対応でき、全体のシステムが過度に複雑化することなく個別に進化し続けることが可能になります。イベントやメッセージングはマイクロサービスには欠かすことができないものです。

私がイベントやメッセージングが必要と考えるに至った危機感がいくらかでも共有しできたでしょうか。名詞を集めてデータ構造を定義していく方法ははじめはパズルを解くような楽しさもありますが、選択肢が少なく、どこか予め決められた道を歩かされているような窮屈さがあります。それはレールが続いているのか崖になっているのかも分からないジェットコースターにチーム全体がのって進まされているような恐怖とでもいいましょうか。要件が複雑であったりシステムが進化していくことを前提とすると、こうした名詞集めによるモノリシックなモデルにはずっと限界を感じてきました。より完璧なデータ構造を求めて残業を続けたことも、大きな手戻りを恐れて名詞を集め続けながら開発フェーズに進めなかったことも、データ構造のモデルが変化するような要件が発覚しないよう不安を抱えて祈り続けたことも、これらは全て自分の苦い経験ではありますが、おそらく責任ある多くのエンジニアにも似た経験があるのではないかとも思います。イベントやメッセージングにも特有の難しさはあり、手放しで簡単だというつもりはありませんし、確実な未来もわかりません。しかしこの希望編から、もっとモデルに選択肢があるかもしれない、もっと柔軟にモデルを描いてもいいかもしれない、イベントやメッセージングを導入してもよいかもしれない、と少しでも考えるきっかけになれば幸いです。

参考文献

[PR]

  • オープンソースのApache Kafkaをベースとした製品として、Red HatからAMQ Streamsが提供されています。AMQ Streamsにはさらに、KubernetesやOpenShiftの上でApache Kafkaの運用の自動化を実現する、オープンソースのStrimziをベースとした機能も含まれています。Red Hat製品に興味ない方もKubernetesを利用している方はStrimziをぜひ使ってみてください。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。