Skip to main content

DTO の型名に `Dto` サフィックスを付けるべきか

TL;DR​

  • 「Dto を付ける」習慣は エコシステム依存 で、一律に「一般的」とは言えない。
  • 付けるのが標準:NestJS、Spring/Java、OSGi Core 仕様、Prisma NestJS DTO ジェネレータのような フレームワーク/規約が命名を前提にしている領域。
  • 付けないのが推奨:Ben Abt (.NET)、Ted M. Young (Java/Hexagonal)、Kevlin Henney、Ardalis らの 設計論寄りの識者。彼らは Dto を「パターン名を型名に貼るのは冗長」と見る。
  • 反サフィックス派の代替案は収束していて、役割ベース命名(CreateUserRequest / UserView / TransferMoneyCommand / MoneyWasSent)に置き換える。
  • 判断軸は 3 つ:(1) エコシステムの慣例に従うか (2) 転送の役割が名前で明確化できるか (3) プロジェクト規模での視覚的ノイズ。

このドキュメントの射程​

TypeScript/Java/.NET を中心に「DTO の型名に Dto(または DTO)サフィックスを付ける慣行がどの程度一般的か」を、英語・日本語両コミュニティを横断して調査した結果をまとめる。フレームワーク固有の事情と、設計論的な原理原則が分かれる典型例なので、どちらが正しいか ではなく どのレイヤーの議論か を切り分けることを目的とする。

「付ける」派が支配的な領域​

NestJS エコシステム​

事実上のデファクト。公式ドキュメント・チュートリアル・記事のほぼ全てが CreateUserDto, UpdateUserDto, ResponseUserDto の形式を採用している。ランタイムリフレクションのため DTO はクラスで表現される必要があり、フレームワーク自体が Dto 命名を前提にしている面もある。

コード生成の世界でも固定化していて、Prisma NestJS DTO ジェネレータのデフォルト設定は以下:

// prisma-generator-nestjs-dto のデフォルト設定
generator nestjsDto {
createDtoPrefix = "Create"
updateDtoPrefix = "Update"
dtoSuffix = "Dto"
// → CreateUserDto, UpdateUserDto, UserDto が自動生成される
}

Java / Spring 系​

日本の実務教材でも「DTO の命名規則:ファイル名は Dto または Bean で終える。例:UserDto, UserBean」と明記される慣行。JPA Buddy(IntelliJ プラグイン)は (?.*)Dto の正規表現で MyEntityDto と MyEntity を自動関連付けする機能を持つほど、命名規則としてツールに組み込まれている。

OSGi Core 仕様​

最も形式化されていて、org.osgi.service.foo.Widget に対する DTO は完全修飾名で org.osgi.service.foo.dto.WidgetDTO と規定されている。パッケージ名末尾に .dto、型名末尾に DTO。

「付けない」派の主張​

Ben Abt (.NET, Medialesson)​

DTO はアンブレラ・タームであり設計パターンだが、ソフトウェア設計の名前として "DTO" が現れることはない。

// Don't do this!
public class MySettingsDTO { ... }

// Do this!
public class ApplicationSettings { ... }

Ted M. Young(Java, Hexagonal Architecture)​

もう Dto サフィックスは付けない、他に良い名前が思いつかない時だけ使う、という方針転換。用途に応じて型名を分ける:

// ブラウザ表示用(Query 側の結果)
public record MemberView(String id, String firstName, ...) { }

// HTTP リクエスト用(Command 側の入力)
public record CreateMemberRequest(...) { }

// DB 永続化オブジェクト(別軸のサフィックス Dbo を導入)
public record MemberDbo(...) { }

Ardalis (Steve Smith)​

中間的。「単に Dto を付けるのが分かりやすい。これで問題ないし最も基本的な表現には十分だが、より記述的な名前を使うべきケースも多い」。

Kevlin Henney / Bertil Muth​

パターン名をクラス名に付ける慣行そのものを疑う立場。FooFactory, BarController, UserRepository を、より語る名前 (Users, ManageUsers, CreateProfile) に置き換える実験。

反サフィックス派の代替解:役割ベース命名​

面白いのは、反サフィックス派の代替案がほぼ収束していること。転送の役割そのものを型名に埋め込む:

DTO の役割型名の例
HTTP リクエストCreateUserRequest, UpdateUserRequest
HTTP レスポンスUserResponse, UserView
CQRS コマンドTransferMoney, RegisterUser
CQRS クエリFindUserById, ListActiveUsers
ドメインイベントMoneyWasSent, UserRegistered(過去形)
DB 永続化UserDbo, MemberDbo(Ted Young 方式)

Andrew Cairns は CQRS の視点から、TransferMoney や MoneyWasSent を「役割で名前が語られている DTO」と整理していて、この視点だと Dto は意味的に冗長になる。

避けるべきサフィックスの共通見解:Data, Info, Vo(Value Object と混同、曖昧)。

判断軸​

(1) エコシステム順応 NestJS / Spring / OSGi の中で書いているなら、周囲との一貫性 > 設計論。Dto を付けるのが正解。フレームワークがそれを前提にしていることもある。

(2) 転送の役割が名前で明確化できるか 役割が明確なら(HTTP リクエスト、コマンド、ビュー、イベント)役割名の方が優れる。「単にレイヤーを跨ぐデータ」以上の情報がない場合は Dto を付ける方が正直。

(3) プロジェクト規模と視覚的ノイズ 規模が大きくなるほど「全クラス名が Dto で終わる」ことが可読性を下げる。役割ベース命名の方がスケールする傾向。

DDD / 関数型ドメインモデリング視点の補足​

Hexagonal Architecture の文脈では 境界に置くもの として DTO を明示する意義はあるが、その識別は「型名のサフィックス」より モジュール境界と変換関数(toDomain / fromDomain, parse / serialize)で表現する方が構造的。

Scott Wlaschin 的な関数型ドメインモデリングの世界だと、境界での DTO は基本的にプリミティブに近い形の record 型、ドメイン側は smart constructor 経由の branded type、という 型そのものの差 で区別する。この場合、名前で区別する必要性はさらに下がる:

// 境界(DTO 相当)
type UserApiPayload = {
readonly id: string
readonly email: string
}

// ドメイン
type User = {
readonly id: UserId // branded
readonly email: EmailAddress // branded, smart constructor 経由
}

// 境界での変換関数がレイヤー分離を担う
const parseUser: (payload: UserApiPayload) => Result
const serializeUser: (user: User) => UserApiPayload

まとめ​

  • Dto サフィックスは 一般的というより 文化圏依存。フレームワーク文化圏では標準、設計論文化圏では非推奨。
  • 反サフィックス派が推すのは 役割ベース命名(Request/Response/Command/Query/View/Event)。これは DTO を否定するのではなく「DTO の何であるか」を型名に埋め込む方針。
  • 型システムでレイヤー分離できるなら、名前の区別への依存はさらに下がる。

参考​