コンテンツにスキップ

【比較】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/