異なる文脈同士の間の腐敗防止層(モデル変換層)
エピックサーガとおとぎ話サーガの違いを深く考えていて、ある時
同期通信のオーケストレーションサーガでは、オーケストレーターと各マイクロサービス間のに腐敗防止層(モデル変換装置)なる者は用意した方が良いのではないか?
という問いを思いつきました。
結論から先に言うと、用意することが推奨されます。
なぜなら、そうでないと分離した意味がないからです。
必須ではありませんが、オーケストレーターと各マイクロサービスが
異なるドメインモデルや関心事を持つ「境界づけられたコンテキスト」 である以上、
腐敗防止層を設けることは、システムの保守性と自律性を保つためのプラクティスだと思います。
ただし、闇雲に腐敗防止層を定義したらいいというわけではないです
今回は、そんなエピックサーガのような同期通信かつアトミックな
分散モノリス状態のマイクロサービスを疎結合にするために、どういうステップで腐敗防止層を設けたらいいのか?
そして、その際の 注意事項 を書いていきます。
なぜ腐敗防止層が有効といえるのか
1. 責務の分離
オーケストレーターの関心事
ビジネスプロセス全体の 手順(シーケンス) を管理すること。
「Aをして、次にBをして、失敗したらCをする」といったフロー制御が主な責務です。
いうならば、Facadeコントローラーを各種エンティティから分けるみたいなものです。
各マイクロサービスの関心事
自身のドメインにおけるビジネスルールを厳格に実行すること。
「Aという指示を、どのようにドメインのルールに従って正しく実行するか」が責務です。
腐敗防止層は、この 「プロセス中心」のオーケストレーターの世界観 と、
「ドメイン中心」のマイクロサービスの世界観との間で、通訳やモデルの変換を行う役割を担います。
2. ドメインモデルの保護
マイクロサービスは、自身のドメインモデルを純粋に保つべきです。
オーケストレーターが扱うデータ構造(プロセスの進捗管理に必要な情報など)が、
マイクロサービスのドメインモデルに直接侵入してくると、モデルが不必要に複雑化し、「腐敗」してしまいます。
腐敗防止層は、オーケストレーターからの指示を、自身のドメインモデルが理解できる形式に変換することで、この腐敗を防ぐ防壁として機能します。
具体例:注文プロセス
オーケストレーターが送るコマンド
プロセスの進捗に必要な、比較的単純なデータ構造です。
ProcessPaymentCommand { orderId, customerId, amount, currency }
決済サービス側の腐敗防止層の役割
①. オーケストレーターから ProcessPaymentCommand を受け取ります。
②. 受け取った customerId を使って、自身のデータベースからリッチなドメインオブジェクトである Customer を取得します。
③. amount と currency から、Money という値オブジェクトを生成します。
④. 自身のドメインロジック(例: PaymentService)のメソッド execute(Customer customer, Money amount) を呼び出します。
このように、ACLが「翻訳」を行うことで、決済サービスのコアなドメインロジックは、オーケストレーターの都合(どのようなデータ構造でコマンドを送ってくるか)を一切意識することなく、自身のビジネスルールに集中できるのです。
最初から定義するべきではない
エピックサーガへ移行したての頃は、オーケストレーターと各マイクロサービスは、
事実上まだ一つのチームが、一つの大きな「分散モノリス」を開発している状態に近いです。
つまり、このアーキテクチャでは密なコミュニケーションは避けられない段階です。
この段階で、分業したいからという短絡的理由で、無理に腐敗防止層を導入することが、かえって混乱を招く可能性があります。
その理由と、対処法を書いていきます。
移行初期に腐敗防止層が混乱を招く可能性がある理由
1. 境界が未確定である
モノリスから分割された直後は、オーケストレーターと各マイクロサービスの責務の境界線がまだ曖昧で、頻繁に変化します。
不安定な境界の間に翻訳層となる腐敗防止層を設けても、境界が変わるたびに腐敗防止層の定義も修正する必要があり、開発の手戻りコストが増えるだけです。
2. チームの認知がまだ一体である
移行初期は、チーム全員がモノリス全体の知識を共有しており、
「オーケストレーターのモデル」も「マイクロサービスのモデル」も、まだ同じチームの頭の中にあります。
この段階では、腐敗防止層による厳格なモデルの分離は、思考のオーバーヘッドを増やすだけの、過剰なエンジニアリングと感じられる可能性があります。
しかも認知の世界で変にコンテキスト境界が定義されかねません。
腐敗防止層定義のための段階的な導入というアプローチ
したがって、より現実的なアプローチは、以下のような段階を踏むことです。
フェーズ1(移行初期): 直接的な連携
まずは、オーケストレーターとマイクロサービス間で、シンプルなDTOを共有し、直接的に連携します。
ここでの最優先事項は、分散システムとしての通信を安定させ、サービス分割の妥当性を検証すること。
一時的に両者が密結合(ともにモデリングなどを使った対話時間が長い)になることは、意図的な技術的負債として許容しないといけません。
フェーズ2(安定・成熟期): 腐敗防止層の導入
サービスの境界が安定してきて、各マイクロサービスが独自の豊かなドメインモデルを育て始めた段階で、腐敗防止層を導入します。
この段階に至って初めて、オーケストレーターの都合からマイクロサービスのドメインモデルを「保護」する必要性が明確になり、腐敗防止層の導入が真の価値を発揮します。
この分離の過程で、マイクロサービスは自身のドメインを保護するために、より自律的な振る舞いを定義する必要が出てきます。
その結果、「注文を受け付ける」という操作だけでなく、
それを取り消す「注文をキャンセルする」といった補償にあたる操作も、サービスの正式なAPIとして定義されることが促されます。
おとぎ話サーガへ進化
移行の容易化
ここからさらに腐敗防止層の導入によって、各マイクロサービスにはすでに補償操作が準備されています。
変更範囲の限定
その結果、エピックサーガからおとぎ話サーガへの移行作業は、もはやマイクロサービス側の大きな変更を必要としません。
主な変更はオーケストレーター側のみに限定されます。
変更前:失敗時に、トランザクションマネージャーに「ロールバック」を指示す
変更後:失敗時に、各マイクロサービスの補償API(cancelOrderなど)を呼び出す
おさらい
腐敗防止層は、オーケストレーターとマイクロサービスの間の技術的な結合を、
ビジネス上の契約(API)による疎結合へと変換します。
これにより、トランザクションの整合性を保つ仕組み(アトミック整合か、結果整合か)の変更が、マイクロサービスのコアロジックに影響を与えなくなります。
腐敗防止層を先に段階的に導入しておくことで、結果整合性への移行が、より安全で容易なステップとなるのです。
「アーキテクチャのベストプラクティスを盲目的に適用するのではなく、
プロジェクトのフェーズやチームの成熟度に応じて、適切なタイミングで段階的に導入する」というのを是非心がけてください。