はじめに
マイクロサービスやモジュラモノリスを採用するとき、「どこで分割するか」が必ず議論になる。一般的にはドメインを前提とした分割が定石とされる。
しかし実務では、ユースケース単位や画面単位、あるいは既存テーブルの都合そのものが境界になっているケースも珍しくない。そうした境界は時間とともに歪み、データの持ち方の破綻やサービス間通信の肥大化を招く。
ではそもそも、なぜドメインで境界を考えるべきなのか。ドメインとは何であり、なぜそれが境界として機能するのか。この前提を共有しないまま、「ドメインで切る」という言葉だけが流通している状況は少なくない。
本稿では次の3点を整理する。
- ドメインとは何か
- なぜドメインが設計の境界として機能するのか
- なぜWeb開発ではドメインが見えにくくなりやすいのか
ドメインとは何か
技術ではなく現実世界の関心事
ドメインとは、ソフトウェアが解決しようとしている現実世界の活動領域そのものを指す。そこには業務のルール、関係者が使う言葉、守るべき制約、意思決定の単位が含まれる。
重要なのは、ドメインがDBのテーブルでもAPIのエンドポイントでもないという点である。テーブル設計はドメインを表現した結果にすぎず、ドメインそのものではない。
「商品」は1つではない
例として「商品」という言葉を考える。
- 販売の文脈における商品は、価格、キャンペーン、表示可否が関心事である
- 物流の文脈における商品は、重量、寸法、在庫ロケーションが関心事である
- 調達の文脈における商品は、仕入先、原価、リードタイムが関心事である
同じ「商品」という言葉を使っていても、関心事はほとんど重なっていない。販売側が価格を変えても物流側は影響を受けないし、物流側が保管棚を変えても販売側は何も変わらない。
つまりドメインを意識するとは、現実世界の活動の境界線をそのままコードの境界線へ写し取る営みである。ドメイン駆動設計ではこれを境界づけられたコンテキスト(Bounded Context)と呼ぶ。詳しくは「境界付けられたコンテキストとは」を参照されたい。
なぜドメインを軸に設計するとうまくいくのか
理由1: 変更の理由が一致する
画面のレイアウトやキャンペーン施策は頻繁に変わる。一方で「注文とは何か」「在庫とは何か」というコアな概念は、そう簡単には変わらない。
ユースケースで境界を切ると、変更頻度のまったく異なるものが同居する。半年で消えるキャンペーンのロジックと、10年使う注文のルールが同じモジュールに入り、片方の変更が常にもう片方のリスクになる。
単一責任の原則は「モジュールは1人のアクターに対して責務を負う」と言い換えられる。ドメインで切ると、変更を要求するアクターが自然と揃うため、この原則がモジュール単位で成立しやすい。
理由2: 言葉の意味とデータモデルが破綻しない
境界を引かないと、「商品」テーブルに販売・物流・調達すべての属性が集まっていく。いわゆる神エンティティである。
カラムは増え続け、どの属性が誰のものかという情報は失われる。NULLを許容するカラムが増え、「この条件のときだけ有効」という暗黙のルールがコードの各所に散らばる。やがて誰もそのテーブルの全体像を説明できなくなる。
ドメインごとに境界を引けば、それぞれの内部では言葉の意味が1つに定まる。境界を越えるときにだけ翻訳すればよく、翻訳の責任もはっきりする。
なお、共通化そのものが悪いわけではない。技術的関心事とドメインロジックでは共通化の効き方が違う。この非対称性については「アーキテクチャ設計における共通化の罠」で扱った。
理由3: 整合性の境界が綺麗に閉じる
強い整合性が必要なルールは、たいていドメインの内側に閉じている。「注文明細の合計と請求金額は一致しなければならない」というルールは、注文ドメインの中だけで守れる。
一方で「注文が確定したら出荷指示を作る」というルールは、ミリ秒単位で同期している必要はない。数秒遅れても業務は成立する。
つまりドメイン境界は、そのままトランザクション境界の候補になる。境界の内側は強整合、境界の外側は非同期メッセージによる結果整合とすれば、疎結合と拡張性を両立できる。
逆に画面単位で境界を切ると、1つの操作が複数サービスへの同期書き込みを要求する。結果として、最も避けたい分散トランザクションの問題に突き当たる。
なぜWeb開発ではドメインが見えにくくなってきたのか
要因1: CRUDとActive Recordの成功体験
Ruby on RailsやLaravelに代表されるフレームワークは、「DBテーブル=モデル」という直感的な対応づけによって開発速度を劇的に引き上げた。マイグレーションを書けばモデルができ、スキャフォールドを叩けば一覧と詳細と編集画面が出来上がる。
これは失敗ではなく、明確な成功である。Webの爆発的な普及を支えたのはこの生産性だった。
問題は、この成功が「DBスキーマこそがドメインである」という錯覚を生みやすい点にある。スキーマは永続化の都合を色濃く反映する。正規化、インデックス、外部キー制約は、業務の関心事ではなく保存と検索の関心事だ。
錯覚に気づかないままサービスが成長すると、業務上まったく別物である概念が、たまたま同じテーブルに載っているという理由だけで一体化してしまう。
要因2: 画面と画面遷移を起点とする開発プロセス
Web開発の多くはデザインカンプとユーザーストーリーから始まる。「この画面でこのボタンを押すと何が起きるか」は具体的で、関係者全員が議論に参加できる。
その結果、コードの構造も画面に引きずられる。画面ごとのコントローラ、画面ごとのAPI、画面ごとのモジュールという分け方は自然に見える。
しかし画面は、システムの中で最も変わりやすい層である。マーケティング施策やA/Bテストのたびに変わる。変わりやすいものを境界の基準に選べば、境界も同じ頻度で壊れる。
要因3: Webビジネスの不確実性とスピード
Web事業はプロダクトマーケットフィットに到達するまで、何が正解かが分からない。作った機能の大半は捨てられる可能性がある。
この段階で時間をかけてドメインをモデリングしても、ドメインごと捨てることになりかねない。そのため初期にモデリングを省く判断は、多くの場合において合理的である。
問題は判断そのものではなく、その判断を見直すタイミングを誰も決めていないことにある。「今はスピード優先」という前提だけが、事業が安定したあとも残り続ける。
要因4: モデリング自体の難易度が高い
ライブラリの使い方には正解があり、ドキュメントを読めば分かる。しかしドメインモデリングに正解はない。
必要なのは、業務担当者の頭の中にある暗黙知を言葉にし、対立する解釈を突き合わせ、合意を取る作業である。技術的なスキルというより、対話と整理のスキルに近い。
さらに成果が見えにくい。良いモデルは「後から効いてくる」ものであり、その場では何も動くものが増えない。評価されにくい作業は後回しになる。
こうした難しさを下げる手段として、関係者を集めてイベントの流れを可視化するイベントストーミングのような手法がある。
どう向き合っていくべきか
最初から完璧なドメインモデルを目指さない
初期はCRUDとActive Recordで素早く作ってよい。ドメイン知識は、作って運用する過程でしか溜まらない。
大事なのは、リファクタリングの引き金をあらかじめ決めておくことである。例えば次のような兆候が挙げられる。
- 同じ名前のエンティティに、明らかに文脈の異なる属性が同居し始めた
- 1つの機能追加で、無関係なはずのコードを壊す頻度が上がった
- チームが増え、同じテーブルに複数チームが書き込むようになった
- テーブルの意味を説明できる人が特定の1人に偏り始めた
画面ではなくデータの整合性とライフサイクルを見る
境界を探すときは、「どの画面に出るか」ではなく「そのデータは誰が生成し、誰が変更し、いつ役目を終えるか」を問う。生成と変更の責任者が一致するかたまりは、ドメインの候補になる。
ユースケースは境界を決めるためではなく、決めた境界の妥当性を検証するために使う。主要なユースケースをなぞったときに、境界を何度も往復する通信が必要なら、境界の引き方を疑うべきだ。
チームとコードの境界を揃える
コンウェイの法則が示す通り、システムの構造は組織のコミュニケーション構造に似る。ドメイン境界とチーム境界がずれていれば、どちらかが必ず歪む。
ドメイン境界はシステムの都合だけで決まるものではなく、チームの認知負荷を下げるための境界でもある。1つのチームが抱えきれない大きさのドメインは、そもそも分割の単位として機能しない。
まとめ
本稿の内容を整理すると次の通りである。
- ドメインとは、技術ではなく現実世界の業務の関心事、言葉、ルールそのものである
- 変更の理由、言葉の一貫性、整合性の範囲が揃うため、ドメインは設計の境界として機能する
- CRUD文化の成功、画面起点の開発、事業の不確実性、モデリングの難しさが、Web開発でドメインを見えにくくしてきた
- 初期はスピードを優先してよいが、モデリングに移る引き金をあらかじめ決めておく
Webの進化を支えたCRUDと画面起点の手法は、その段階においては正しかった。しかしサービスが複雑化し、チームの数も増えた先では、ドメインへ立ち返る姿勢こそ持続可能な開発の鍵になる。