【比較】DTO・エンティティ・値オブジェクトの違い¶
概要¶
オブジェクト指向/レイヤード設計でよく出てくる「DTO」「エンティティ」「値オブジェクト(Value Object, VO)」は、名前が似ていて混同されがちですが、役割もレイヤーも判定基準もまったく別物です。DTO(Data Transfer Object)は、プロセス境界をまたいでデータを運ぶための入れ物で、振る舞いを持たず、シリアライズ可能な形にしておくのが要点(Martin Fowler の定義)。エンティティ(Entity)は、識別子(アイデンティティ)と時間を通じた連続性・ライフサイクルを持つドメインオブジェクトで、状態は変わっても「同じ存在」であり続け、等価判定は ID で行います(DDD / Eric Evans)。値オブジェクト(Value Object)は、概念的な識別子を持たず、属性の組み合わせがすべてで、不変(イミュータブル)・値による等価判定・丸ごと置換が原則です(DDD / Martin Fowler)。
この記事は、3 者を「どのレイヤーで・何のために使うか」という観点で切り分けます。特に DTO をエンティティや値オブジェクトと取り違える(あるいは逆に DTO をドメインに持ち込む)という典型ミスを、出所の定義に立ち返って整理するのがねらいです。
例えるなら:書類の「送付状」と、人の「戸籍」と、お金の「金額」¶
- DTO … 部署をまたいで書類を渡すときの「送付状つきの封筒」。中身を運ぶことだけが仕事で、それ自体は判断(振る舞い)をしない。封筒の形(フォーマット)は受け渡し相手に合わせて決める。
- エンティティ … 一人ひとりの「戸籍」。名前や住所が変わっても「同じ人」であり続け、同一性は属性ではなく ID(戸籍上の identity)で判断する。
- 値オブジェクト … 「1,000 円」という金額そのもの。どの 1,000 円札かは問題ではなく、額面が同じなら等価。書き換えるのではなく別の金額に「置き換える」。
詳細比較¶
| 比較軸 | DTO(Data Transfer Object) | エンティティ(Entity) | 値オブジェクト(Value Object) |
|---|---|---|---|
| 出所・文脈 | Fowler『PoEAA』のパターン(分散/境界越え) | DDD(Eric Evans)のドメインモデル要素 | DDD(Evans)/Fowler bliki のドメインモデル要素 |
| 主な目的 | プロセス境界をまたぐデータ転送・呼び出し回数の削減 | ドメイン上の「個」を識別し続ける | ドメイン上の「値・量・記述的側面」を表す |
| 識別子(identity) | 持たない(単なるデータの束) | 持つ(時間を通じて一貫する ID) | 持たない(属性の組み合わせがすべて) |
| 等価判定 | 通常は意味を持たせない(運搬用) | ID(アイデンティティ)で等価 | 全属性の値が等しければ等価 |
| 可変性 | 実装次第だが振る舞いは持たせない | 可変(状態は変わるが ID は不変) | 不変(変更時は新インスタンスに置換) |
| 振る舞い(ロジック) | 持たない(データキャリア) | ドメインの振る舞いを持つ | 値に閉じた振る舞いは持てる(不変の範囲で) |
| シリアライズ | 前提(ワイヤを越える形にする) | 必須要件ではない | 必須要件ではない |
| 典型レイヤー | 境界(API/リモート/プレゼンテーション⇔アプリ) | ドメイン層 | ドメイン層 |
| 代表例 | API レスポンス用オブジェクト、リクエストボディ | User、Order、Account(ID で追える対象) | Money、DateRange、住所、座標 |
よくある誤解¶
- 誤解1:「DTO とエンティティは同じもの(DB の 1 行=そのまま API で返す)」 — 別物です。DTO は Fowler の定義で「プロセス間でデータを運び、呼び出し回数を減らすためのオブジェクト」であり、ワイヤを越えるためシリアライズ可能であることが前提。一方エンティティは DDD で「属性ではなく、時間を通じた連続性と同一性(identity)によって定義される」ドメインオブジェクトです。エンティティをそのまま境界に晒すと、永続化都合・公開都合・ドメイン都合が 1 クラスに混在し変更が伝播します。境界には DTO、ドメインにはエンティティ、と層で分け、間をマッピング(アセンブラ/マッパー)でつなぐのが本来の形です。
- 誤解2:「値オブジェクトは“ただの小さなデータ構造”だから可変でよい/DTO と同じ」 — 違います。Fowler は「値オブジェクトは不変であるべき(さもないとエイリアシングのバグに悩まされる)」とし、変更は「オブジェクトを書き換えるのではなく、新しいものに置き換える」と明言しています。等価判定も“参照”ではなく“値”で行うのが値オブジェクトの定義です。可変にしたり参照同一性で比べたりした時点で、それはもう値オブジェクトの利点(安全な共有・置換可能性)を失っています。DTO は運搬用、値オブジェクトはドメインの「値」概念であって、目的もレイヤーも別です。
- 誤解3:「ID(主キー)さえあれば、それはエンティティだ」 — 主キーを持つこと自体が本質ではありません。DDD では「オブジェクトが属性ではなく同一性によって区別されるとき、その同一性をモデル定義の中心に据える」のがエンティティです。逆に、識別子で“追いかけ続ける必要がない”概念(金額・期間・座標など)は、ID を付けず値オブジェクトにする方が適切です。「DB に主キーがあるから全部エンティティ」と決めつけると、本来イミュータブルにできる値まで可変な“なんちゃってエンティティ”になり、不変性・置換可能性という値オブジェクトの利点を捨てることになります。
- 誤解4:「DTO に getter/setter 以外のロジック(バリデーション・計算・ドメインルール)を入れてよい(“リッチ DTO”)」 — DTO の本分はデータの運搬であり、振る舞い(ドメインロジック)を持たせるとそれは DTO ではなく別物(しばしばエンティティ/値オブジェクトの責務)になります。Fowler は DTO の利点として「シリアライズの仕組みをカプセル化し、その種のロジックをコードの他の部分から締め出せる」点を挙げています。ドメインルールはエンティティ/値オブジェクト側に置き、DTO は境界で形を合わせるだけにするのが分離の意図です。
実務での選び分け¶
- API/外部・リモートとデータをやり取りする箇所 → DTO。リクエスト/レスポンスの形に最適化し、振る舞いは持たせない。ドメインとの間はマッパーで変換する(ドメインを境界に直出ししない)。
- 「同じ人/同じ注文」を時間を通じて追跡したい → エンティティ。同一性を ID で定義し、状態変化はあっても ID で一貫させる。等価判定は ID ベース。
- 金額・期間・座標・住所など“値そのもの” → 値オブジェクト。不変にし、値で等価判定し、変更時は新インスタンスへ置換。エンティティの属性として持たせると安全。
- 「DB の行をそのまま JSON で返したい」誘惑 → 境界には DTO を一枚かませる。永続化モデル/ドメインモデル/公開モデルの変更理由が違うため、結合させない。
- 判断に迷ったら 3 つの問い →「①境界を越えて運ぶだけか?→DTO」「②時間を通じて同一性を追うか?→エンティティ」「③属性が同じなら交換可能か(不変でよいか)?→値オブジェクト」。
- 過剰なマッピングが負担に感じる小規模ケース → 層を薄くするのは設計判断としてあり得るが、「DTO=エンティティ=VO」を概念として混同したまま 1 クラスに統合するのとは別問題。区別を理解した上で、意図して簡略化する。
ひとことまとめ¶
DTO は「境界をまたいでデータを運ぶ、振る舞いなし・シリアライズ前提の入れ物」、エンティティは「同一性とライフサイクルを持ち、状態が変わっても ID で“同じ存在”であり続けるドメイン要素」、値オブジェクトは「識別子を持たず、不変・値等価・丸ごと置換が原則の“値”概念」。3 者は名前が似ているだけで、使うレイヤーも判定基準も別物——ここを取り違えないのが要点です。
出典・参考¶
- Martin Fowler『P of EAA Catalog』Data Transfer Object(「プロセス間でデータを運び呼び出し回数を減らすオブジェクト」「シリアライズの仕組みをカプセル化」): https://martinfowler.com/eaaCatalog/dataTransferObject.html
- Martin Fowler bliki「ValueObject」(「値オブジェクトは不変であるべき」「変更は書き換えではなく新インスタンスへの置換」「参照ではなく値で等価判定」): https://martinfowler.com/bliki/ValueObject.html
- Martin Fowler bliki「EvansClassification」(エンティティ=時間を通じた distinct identity を持つ/値オブジェクト=属性の組み合わせとしてのみ意味を持つ・不変): https://martinfowler.com/bliki/EvansClassification.html
- Eric Evans「Domain-Driven Design Reference」(公式 DDD リファレンス。Entity=同一性と連続性で定義/Value Object=概念的同一性を持たない記述的側面。CC BY 4.0): https://www.domainlanguage.com/ddd/reference/