はじめに
新しいプロジェクトに配属された時、CreateUserDto、UpdateUserDto、User(Entity)、UserResponseなど似たような型定義がいくつもあり、「これ全部必要なの?」「どう使い分けるの?」と戸惑った経験があります。
本記事では、DTOとEntityの役割を整理し、「なぜ複数の型定義が存在するのか」「いつ使うべきか・使わなくてもいいか」の判断基準をまとめました。
そもそもDTOとEntityは「必須」なのか?
結論から言うと、必須ではありません。
言語やフレームワークの文化によって使われ方が異なりますが、TypeScriptでは type や interface が強力なため、わざわざDTOやEntityというクラスを作らないプロジェクトも多くあります。
例えば、以下のコードはどちらも有効です。
// パターンA: DTO / Entity を使う
export class CreateUserDto { ... }
export class User extends Entity { ... }
// パターンB: type だけで済ませる
export type CreateUserInput = { ... }
export type User = { ... }
どちらが正解ということはなく、プロジェクトの規模や要件によって選択します。
DTOとは何か?
DTOは「Data Transfer Object」の略で、データを運ぶ形です。APIの入力・出力データが代表例です。
// これはDTO(名前がDtoでなくても)
type CreateUserInput = {
name: string;
email: string;
}
// これもDTO
type UserResponse = {
id: string;
name: string;
createdAt: string;
}
重要なのは、DTOはただのデータの入れ物ということです。バリデーションは含むかもしれませんが、業務ルールは持ちません。
〇〇Dto というファイル名や拡張子はルールではなく、プロジェクトの自由です。CreateUserInput や UserResponse という名前でも、役割としてはDTOです。
Entityとは何か?
Entityは業務ルールを守る存在です。Entity自体はDBに直接アクセスせず、
「どう振る舞うべきか」というルールだけを持ちます。ただの型定義ではありません。
- 型定義
- 「nameは文字列」「emailは必須」など、データの形をチェック
- Entity
- 「アクティブユーザーは削除できない」「停止→削除の順番で変更」など、ビジネスのルールを管理
例えば、ユーザーのステータス管理を考えてみましょう。
// ただの型(これはEntityではない)
type User = {
id: string;
status: 'active' | 'suspended' | 'deleted';
}
// Entity(業務ルールを持つ)
class User {
private status: UserStatus;
suspend() {
if (this.status === 'deleted') {
throw new Error('削除済みユーザーは停止できません');
}
this.status = 'suspended';
}
delete() {
if (this.status === 'active') {
throw new Error('アクティブなユーザーは削除できません');
}
this.status = 'deleted';
}
}
ZodがあるならEntityはいらない?
「Zodでバリデーションできるから、Entityいらないのでは?」と思うかもしれません。しかし役割が異なります。
Zodは「データが正しい形か」をチェックしますが、「この状態からこの状態に変更していいか」は管理しません。両者は補完関係にあります。
Prismaを使っている場合の考え方
Prisma(ORMツール)を使っている場合、Entityを作らない設計も一般的です。
Prismaはスキーマから型安全なモデルを自動生成するため、「DB構造=型定義」として扱えるのが強みです。
import { User } from '@prisma/client';
// Prismaが生成した型をそのまま使う
const user: User = await prisma.user.findUnique({ where: { id } });
複雑な業務ルールがない限り、Prismaの型で十分です。「将来のため」にEntityを用意する必要はありません。
type / interface だけでいいケース
以下のようなプロジェクトでは、type や interface だけで十分です。
- CRUD中心のアプリケーション
- 業務ルールが薄い
シンプルなプロジェクトに複雑な設計を持ち込む必要はありません。
まとめ
DTO / Entity は役割を表す概念であり、名前やファイル構成はルールではありません。
- DTO: データを運ぶ形(API入出力)
- Entity: 業務ルールを守る存在(状態遷移)
TypeScriptでは、まず type や interface から始めて、必要に応じてクラスに昇格させるアプローチが現実的です。
複雑な設計は複雑な問題にだけ適用するのが、保守しやすいコードを書く秘訣です。