DTO と Entity は「役割」がまったく違う
DTO と Entity は、どっちも「フィールドを持ったクラス」なので見た目が似ていますが、役割がまったく違います。
DTO(Data Transfer Object)は
「データを“運ぶだけ”の入れ物」です。
Entity は
「システムの中の“登場人物”を表し、自分のルールや振る舞いを持つオブジェクト」です。
同じ「User」という名前でも、
画面や API とのやりとりに使う UserDto
ドメインの中心として振る舞いを持つ User エンティティ
というふうに、役割によってクラスを分けます。
DTO とは何か:とにかく「運ぶだけの箱」
DTO の目的と特徴
DTO は「レイヤの境界を超えてデータを運ぶためのクラス」です。
例えば、
ブラウザ ⇔ サーバー(Web API のリクエスト/レスポンス)
アプリケーション層 ⇔ プレゼンテーション層(Controller など)
サービス間の通信(REST, gRPC など)
こういうところで「JSON にしたり、JSON から復元したり」するための形として使います。
DTO の特徴はだいたいこんな感じです。
フィールドは public か、public な getter/setter だけ
業務ロジック(ビジネスルール)は基本的に持たない
バリデーションを持っても、せいぜい「入力チェック」レベル
つまり「中身を詰めてどこかに渡す」「渡された中身をそのまま読む」が主な仕事です。
DTO の例
例えばユーザ登録 API のリクエスト DTO を考えます。
// Web から JSON で送られてくるデータを受け取るための DTO
public class UserRegisterRequestDto {
public String name;
public String email;
public String rawPassword;
// ライブラリの都合で引数なしコンストラクタが必要になることが多い
public UserRegisterRequestDto() {
}
}
Javaもしくは getter/setter 付きでも構いません。
public class UserRegisterRequestDto {
private String name;
private String email;
private String rawPassword;
public UserRegisterRequestDto() {}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getRawPassword() { return rawPassword; }
public void setRawPassword(String rawPassword) { this.rawPassword = rawPassword; }
}
Javaこのクラス自体は、「ユーザ登録のルール」を何も持っていません。
「名前は必須」「メール形式が正しいか」「パスワードの強度」などのビジネスルールは、別の場所でチェックします。
DTO は「入り口/出口の形」として使い、
ドメインの中心には踏み込ませないのがポイントです。
Entity とは何か:世界の“登場人物”とそのルール
Entity の目的と特徴
Entity は、ドメイン(業務)における「登場人物」を表します。
EC サイトなら「ユーザ」「商品」「注文」「在庫」
勤怠システムなら「社員」「勤務」「休暇」
銀行なら「口座」「振込」「取引履歴」
こういう概念が Entity です。
Entity の特徴はこうです。
ID(同一性)を持つ(UserId, OrderId など)
ビジネスルールをメソッドとして持つ
不変条件(常に満たされるべきルール)を守る責任がある
つまり「ただのデータ」ではなく、「自分のルールを知っているオブジェクト」です。
Entity の例
さっきの DTO と対応する User エンティティを作ってみます。
public final class User {
private final Long id;
private String name;
private String email;
private String passwordHash;
private int point;
public User(Long id, String name, String email, String passwordHash, int point) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("名前は必須です");
}
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("メール形式が不正です");
}
if (point < 0) {
throw new IllegalArgumentException("ポイントは0以上です");
}
this.id = id;
this.name = name;
this.email = email;
this.passwordHash = passwordHash;
this.point = point;
}
public void changeEmail(String newEmail) {
if (newEmail == null || !newEmail.contains("@")) {
throw new IllegalArgumentException("メール形式が不正です");
}
this.email = newEmail;
}
public void addPoint(int added) {
if (added < 0) {
throw new IllegalArgumentException("マイナス加算は禁止です");
}
int result = this.point + added;
if (result > 10_000) {
result = 10_000;
}
this.point = result;
}
public Long id() { return id; }
public String name() { return name; }
public String email() { return email; }
public int point() { return point; }
}
Javaこの User は、
生成時に「名前必須」「メール形式」「ポイント >= 0」を保証する
メール変更時のルールを知っている
ポイント加算のルール(マイナス禁止、上限 10,000)を知っている
というように、「ユーザとしての振る舞い」を自分の中に持っています。
これが Entity の役割です。
DTO と決定的に違うのは、「ビジネスルールを持つかどうか」です。
DTO と Entity はどこでやりとりするのか(重要)
Controller で DTO ⇔ Entity を変換するイメージ
Web アプリケーションを例にすると、流れはこうなります。
ブラウザから JSON リクエストを受け取る
→ DTO にマッピングされる(UserRegisterRequestDto)
→ DTO から Entity を作る(User エンティティ)
→ ドメインロジック(User やサービス)で処理
→ 結果を DTO に変換してレスポンスとして返す
コードにすると、ざっくりこんな感じです。
@RestController
class UserController {
private final UserRegisterService service;
@PostMapping("/users")
UserRegisterResponseDto register(@RequestBody UserRegisterRequestDto request) {
// DTO → Entity への変換
User user = service.register(request.getName(), request.getEmail(), request.getRawPassword());
// Entity → DTO への変換
UserRegisterResponseDto response = new UserRegisterResponseDto();
response.id = user.id();
response.name = user.name();
response.email = user.email();
return response;
}
}
JavaUserRegisterService では、主に Entity を扱います。
class UserRegisterService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
User register(String name, String email, String rawPassword) {
String hash = passwordEncoder.encode(rawPassword);
User user = new User(null, name, email, hash, 0);
return userRepository.save(user);
}
}
Javaここで大事なのは、
Controller(境界)では DTO を使う
Service やドメインロジックでは Entity(や値オブジェクト)を使う
という「レイヤごとの役割分担」です。
DTO をドメイン層に持ち込んでしまうと、
「DTO がそのままビジネスロジックを持ち始める(貧血モデルの拡大版)」という危険な状態になります。
なぜ「違い」を意識しないとヤバいのか(深掘り)
1. DTO にロジックを書き始めると、設計が崩れる
DTO に「軽い気持ちで」ロジックを書き始めると、こうなります。
public class UserDto {
public String name;
public String email;
public int point;
public void addPoint(int added) {
// ここにビジネスルールを書き始めてしまう
if (added < 0) { ... }
...
}
}
Javaいったんこうなると、
Controller でも UserDto を使い
Service 層でも UserDto をそのまま使い
Repository から返すのも UserDto で
というふうに、「DTO と Entity の境界」が消えます。
結果として、
どのレイヤでも DTO がビジネスロジックを持ち
責務の分担があいまいになり
テストや変更の単位が分かりづらくなる
という、重い設計の歪みにつながります。
2. Entity をそのままレスポンスに使うと、境界がにじむ
逆パターンもあります。
Entity をそのまま JSON レスポンスに使ってしまうケースです。
@GetMapping("/users/{id}")
User get(@PathVariable long id) {
return userRepository.findById(id); // Entity をそのまま返してしまう
}
Java短いサンプルなら “楽” に見えるんですが、これも危険です。
レスポンス仕様の変更(項目追加 / 削除 / 名前変更)が Entity に直撃する
Entity の内部事情(カラムや関連)がそのまま API と外部に晒される
クライアントの都合(表示形式)とドメインの都合(不変条件)が混ざる
など、境界の責務がぐちゃぐちゃになります。
DTO を一枚かますのは、「外の都合」と「中の都合」をきれいに分けるためです。
それでも「全部 DTO / Entity 分けるの面倒じゃない?」への現実的な答え
正直に言うと、最初は面倒に感じると思います。
特に小さな CRUD アプリだと、
「DTO と Entity 分ける意味ある?」
と感じる場面もあるでしょう。
現実的なラインとしては、
業務ルールがほとんどなく、ただ入出力するだけの小さなツール
→ Entity もただのデータクラスで済ませることもある
業務ロジックが増えてきて、「どこに何を書けばよいか迷う」ようになってきた
→ DTO と Entity をきっちり分けはじめるタイミング
と捉えるといいです。
大事なのは、
DTO は境界(UI / API / 他システムとのやりとり)でだけ使う
Entity(+値オブジェクト)はドメインの真ん中で使う
という線引きを「意識しているかどうか」です。
まとめ:DTO と Entity をどう見分けるか
DTO と Entity の違いを、一言でまとめるとこうなります。
DTO は「運ぶだけの入れ物」
Entity は「振る舞いとルールを持つ登場人物」
コードを見たときに、
このクラスは、ただデータを詰めて渡すだけでいいのか?
それとも、この概念には守るべきルールや振る舞いがあるのか?
と自問してみてください。
