モデルとテーブルをコンテキストに分割する戦術

アプリケーションが成長して、扱うドメインも大きくなると、どこかでコンテキストの境界をひき、アプリケーションを分割するタイミングがくる。

そんなときに、複数のコンテキストを内包したドメインモデルと、それを永続化するテーブルを分割する戦術について考えてみた。考えただけで、実践したわけじゃないから、そんなにうまくいかないとかあると思う。

ユーザーモデルをコンテキストAとコンテキストBに分割する例で考える。

フェーズ0

初期状態として、ユーザーモデルとそれを保持するテーブルがある。この時点では、すべてのコンテキストを1つのモデルで表現している。

ユーザーモデルとそれを永続化させるテーブル

なお、モデルからテーブルへの矢印はwriteを表し、テーブルからモデルへの矢印はreadを表す。

フェーズ1

次に、モデルのみコンテキストに分割した。2つのモデルはいまだ1つのテーブルに永続化されている。

2つのコンテキストとそれぞれに含まれるユーザーモデル、テーブル

2つのモデルはテーブルを介して暗黙的に依存関係にあり、片方のコンテキストで起きた変更が意図しない結果を他方のコンテキストに及ぼす危険がある。

フェーズ2

各コンテキストのモデルに対応するテーブルを追加した。この時点では、writeは元のテーブルと新しく追加したテーブルに二重におこない、readは元のテーブルからおこなう。次のフェーズで完全にテーブルを移行させるのだけど、安全にread先を切り替えるため、一時的に二重にwriteする体制をとっている。

2つのコンテキストとそれぞれに含まれるユーザーモデルとテーブル、そして独立したテーブル

実装としては、元のテーブルと新しいテーブルのwriteを同一トランザクションで実行できるならそうするし、完全に独立したマイクロサービスになっているなら同一トランザクションが使えないため、元のテーブルと同期させるworkerを別途用意し結果整合性を担保すると思う。

フェーズ3

read先のテーブルを切り替えられたなら、元のテーブルを切り離し、完全にコンテキスト内でモデルとテーブルが完結できる。もし切り替えに失敗しても、元のテーブルにすぐにread先を戻せば影響を最小限に抑えられる。

2つのコンテキストとそれぞれに含まれるユーザーモデルとテーブル

ここまで来てようやくモデルとテーブルをコンテキストに分割することができた。お互いのモデルやテーブルの変更が他方に影響を与えることはなくなった。

まとめ

3段階に分けてコンテキストに分割していく戦術について見てきたけど、1つのモデルを分割するのにここまで手間をかけるのはさすがに骨が折れるとも思ってしまった。もっと効率的に進める戦術はないものか。