ドメイン用語
今回の記事では、ドメイン、ドメインモデル、ドメインオブジェクト、ドメインオブジェクトモデルがそれぞれどう違うのかについて話してみたい。
DDDの記事を読みながら、これらの言葉が同じものを指しているのか迷ったことのあるフロントエンド開発者に向けた記事である。最後まで読めば、4つの用語を抽象から具体へと下る一本の線の上に並べられ、APIレスポンスの型がなぜドメインモデルではないのかを説明できるようになる。
筆者も開発をしながらこれらの言葉にかなり頻繁に触れてきたが、いざ「ドメインとは正確に何なのか?」と聞かれると、明快に答えるのは簡単ではなかった。(正直に言うと、開発を始めたばかりの頃はドメインといえばwwwのことだと思っていた。)例はすべて総合所得税の計算で説明する。
ドメイン(Domain)
最も基本的な問いから始めよう。ドメインとは何だろうか?
Eric Evansは著書 Domain-Driven Design: Tackling Complexity in the Heart of Software(2003) で、ドメインを次のように定義している。
知識、影響力、または活動の領域。
"A sphere of knowledge, influence, or activity."
簡単に言えば、プログラミングによって解決しようとする問題領域そのものがドメインである。税務申告サービスを作るなら「税務申告」がドメインであり、保険請求プラットフォームを作るなら「保険請求」がドメインとなる。ドメインはコードではない。ソフトウェアより先に存在する、現実世界の問題領域である。
これはフロントエンド開発者にとって、どのような意味を持つのだろうか?私たちが作るUIは、最終的にこのドメインをユーザーへ見せ、操作できるようにする 窓(window) である。税金を主なドメインとするToss Incomeや3o3のような税金還付サービスを開発するなら、所得区分、必要経費率、所得控除、税額控除、還付額といったドメインの概念をUIで表現することになる。そのため、フロントエンド開発者も自分が扱うドメインを深く理解しなければならない。UIコンポーネントをうまく描画することと同じくらい、「このサービスが解決する問題は何か」 を知ることが重要だという意味である。
とはいえ、「税金」という一つのドメインだけを見ても、その内部には数多くのサブドメインが存在する。筆者が表面的に把握している総合所得税の計算パイプラインだけでも、次のとおりだ。

このパイプラインの各段階は、それぞれ固有のルールとデータを持つサブドメインである。「税金」という一つの大きなドメインの中で、所得(Income)、控除(Deduction)、税額(Tax)、申告結果(Filing)という細かなドメインが絡み合っている。これらをコード上でどう分けるかが、まさにドメインモデリングの中心的な問いである。
ドメインモデル(Domain Model)
では、ドメインモデルとは何だろうか?ドメインと「ドメインモデル」はどう違うのだろう?
Martin Fowlerは、ドメインモデルを振る舞いとデータの両方を含むドメインのオブジェクトモデルだと定義している。Eric Evansの定義はそこからもう一歩踏み込む。
ドメインの選択された側面を記述する抽象化の体系であり、そのドメインに関する問題を解決するために使用できるもの。(Eric Evans)
A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain.
重要なのは、「選択的な抽象化」 だという点である。ドメインモデルは、現実世界のすべてを含むわけではない。映画監督が現実のすべての場面を収めず、物語に必要な場面だけを選ぶように、ドメインモデルも 解決したい問題に必要な側面だけを選び、構造化したもの である。
ここで大切な点が一つある。ドメインモデルは、必ずしもコードである必要はない。ホワイトボードに描かれた図かもしれないし、チーム内で共有されているメンタルモデル(Mental Model)かもしれない。つまり、ドメインモデルという用語自体は、ソフトウェアとは独立した概念であり得る。
ここには、フロントエンド開発者が特に混同しやすい点がある。APIレスポンスの構造を見て、「これがドメインモデルだ」と考えることだ。しかし、それは データモデル(Data Model) であって、ドメインモデルではない。
データモデルとドメインモデルを区別すると、次のようになる。
| 区分 | ドメインモデル | データモデル |
|---|---|---|
| 目的 | ビジネス上の概念とルールを表現する | 保存・転送の構造を定義する |
| 言語 | ビジネス用語(課税標準、税額控除、還付額) | 技術用語(string、number、array) |
| 構成要素 | データ + 振る舞い(ルール) | データ構造のみ |
| 例 | 「課税標準1,400万ウォン以下の区分は税率6%」 | { taxableBase: number, taxRate: number } |
データモデルは「どのような形でデータがやり取りされるか」を定義し、ドメインモデルは「このデータがビジネス上何を意味し、どのようなルールに従うか」を定義する。 この二つを区別できなければ、コンポーネントがAPIレスポンスの構造へ直接依存し、バックエンドのスキーマが変わるたびにフロントエンド全体が揺さぶられることになる。
ドメインオブジェクト(Domain Object)
ドメインモデルが概念の体系であるなら、ドメインオブジェクトは、その概念をコードとして実装した実体である。
Code with Jasonを運営するJason Swettの記事では、ドメインオブジェクトを次のように定義している。
私のオブジェクトモデルにあるオブジェクトのうち、ドメインモデルにも一つの概念として存在するものを、ドメインオブジェクトと呼ぶ。
Any object in my object model that also exist as a concept in my domain model I would call a domain object.
つまり、ドメインモデルに「総合所得」という概念があり、コード上にIncomeという型があるなら、このIncomeがドメインオブジェクトである。ただし、コード上のすべてのオブジェクトがドメインオブジェクトなのではない。HttpClient、LocalStorageAdapter、useDebounceなどは技術的な道具であり、ドメインの概念ではない。
EntityとValue Object
Evansは、ドメインオブジェクトを Entity、Value Object、Service の三つに分類している。(Martin Fowlerはこの分類を「Evans Classification」と呼ぶ。)Serviceは、「特定のオブジェクトへ自然に帰属しないドメインの操作」を表す別の概念だが、この節の中心はデータをどのように識別するかという問題なので、EntityとValue Objectの二つに焦点を当てる。
Entity(エンティティ) は、時間やさまざまな表現を超えて維持される、一意のアイデンティティを持つオブジェクトである。税務申告書(TaxFiling)、納税者(Taxpayer)、所得記録(IncomeRecord)のように一意なIDで識別され、属性が変わっても同じIDなら同じEntityである。申告書の控除項目が修正されても、申告書IDが変わらない限り、それは同じ申告書であり続ける。
Value Object(値オブジェクト) は、属性の組み合わせだけで意味を持つオブジェクトであり、すべての属性値が同じなら同一とみなす。金額(Money)、税率(TaxRate)、税率区分(TaxBracket)のように、値そのものに意味があるオブジェクトだ。「税率6%」は、どこで使われても「税率6%」でしかない。
この区別は、フロントエンドでなぜ重要なのだろうか?次のコード例で見てみよう。
interface TaxFiling {
id: string;
taxpayerName: string;
taxYear: number;
status: FilingStatus;
}
const isSameFiling = (a: TaxFiling, b: TaxFiling) => a.id === b.id;
interface Money {
amount: number;
currency: "KRW" | "USD";
}
const isSameMoney = (a: Money, b: Money) =>
a.amount === b.amount && a.currency === b.currency;TaxFilingはidをアイデンティティの基準とするため、Entityである。(idフィールドを持つこと自体がEntityの定義なのではなく、「そのidによって同一かどうかを判断する」点が重要である。)Moneyはidを持たず、amountとcurrencyの組み合わせだけで識別され、すべての属性が同じなら同じ値とみなされる。
EntityはIDによる比較、Value Objectは属性による比較。この区別が明確であれば、状態管理で「このデータが同じものか別のものか」を判断するロジックが自然に整理される。リスト内の項目を更新するとき、EntityならIDで探して置き換え、Value Objectならイミュータブルな置き換え(immutable replace)を行う、といった具合だ。
ドメインオブジェクトモデル(Domain Object Model)
「ドメインモデル」と「ドメインオブジェクト」は分かった。では、ドメインオブジェクトモデルとは何だろうか?
調べてみると、意外にも合意された定義はない。多くの文献では、「ドメインモデル」「ドメインオブジェクトモデル」「概念モデル(conceptual model)」「分析オブジェクトモデル(analysis object model)」を 実質的に同義語 とみなしている。オブジェクト指向分析の段階で描かれる概念モデルを指す、複数の呼び方だという立場である。
一方で、もう少し分離された層として捉える見方もある。ドメインモデルが実際のコードへ変換される地点がオブジェクトモデルである、という説明が代表的だ。
この二つ目の観点では、オブジェクトモデルとは、システム内の すべてのコードオブジェクトの構造 である。そこにはHttpClientやuseDebounceのような技術的な道具も含まれる。その中で、ドメインの概念を表すオブジェクトの部分集合と、それらの関係が ドメインオブジェクトモデル である。「Object Model」をシステムの静的構造(クラス、属性、操作、関係)と定義してきた、オブジェクト指向モデリングの伝統にもつながっている。
筆者は、こちらの観点のほうがフロントエンド開発者にとって実用的だと考えている。実際に書くコードには、ドメインオブジェクトと技術的なオブジェクトが常に混在しているからだ。
結局、ドメイン → ドメインモデル → ドメインオブジェクトモデル → ドメインオブジェクト は、抽象から具体へと下りていく階層である。ドメインが最も広く、ドメインオブジェクトが最も具体的だ。したがって、フロントエンドのコードを書くときに実質的に考えるべき領域は、ドメインオブジェクトモデル(ドメインの概念を表す型と、その間の関係)をどのように構造化するか ということになる。
まとめ
まとめると、ドメインは私たちが解決しようとする問題領域であり、ドメインモデルはその問題を選択的に抽象化した概念体系、ドメインオブジェクトモデルは筆者が採った観点ではその概念体系をコードとして実装したもの、そして ドメインオブジェクトはその実装内の個々のオブジェクトである。
この区別がコードでどんな違いを生むのか、つまり税金計算のようなドメインロジックをコンポーネントの外のどこに置き、どこまで分けるのかは、ドメインモデルで続けて扱う。
この記事を読んだ皆さんも、次にAPIレスポンスの型を見たときには「これはデータモデルなのか、ドメインモデルなのか」と一度問いかけてみてほしい。