DTOって何?なんで必要?
Laravelで開発していると、ControllerやServiceの中で
「とりあえず配列で返す」ってこと、よくあります。
return [
'id' => $spot->id,
'name' => $spot->name,
'price' => $spot->price,
];最初はこれで十分だし、
むしろシンプルで好きだったんですよね。
でも、開発が進んでいくうちに
「この配列、どこまで保証されてるんだろ…?」っていう不安が
じわじわ出てきたんです。
- キー名のタイプミスがあっても気づけない
- 値がnullだったり、意図しない型が入ったりする
- 返り値の形がControllerごとにバラバラ
いわゆる「配列地獄」みたいな感じ。
それをどうにかしたくて、調べてるうちに出てきたのが DTO(Data Transfer Object) でした。
目次
DTOとは
ざっくり言うと、
DTOは「層と層のあいだを安全にデータを渡すための箱」らしいです。
クラスとして型を定義して、
「この形で渡します」「この形で受け取ります」を
コードで明確にしておくもの。
比喩で言うと、ちゃんと宛名と封筒がある郵便みたいな感じ。
中身が変わっても封筒の形は変わらないので、
受け取る側が安心できる。
たとえばこんな感じ
final readonly class SpotListItemRow {
public function __construct(
public int $id,
public string $name,
public int $price,
public string $city,
public string $createdAt,
) {}
}このクラスを通してデータを渡すだけで、
ControllerもServiceも「この形で来る」ってわかる。
これが地味だけど、あとで効いてくるんですよね。
なんで必要なんだろう?って考えたときに見えてきたこと
1.「データの形」が契約になる
配列って柔軟すぎて、なんでも入っちゃう。
でもDTOだと、プロパティを固定できるから
「この構造で渡す」という**約束(契約)**ができる。
関数の引数じゃなくて、層と層の“あいだ”で守る契約。
これが意外と安心感ある。
2. テストとリファクタがめちゃくちゃ楽になる
DTOを通しておくと、
Repositoryの実装を変えても、DTOが同じなら他は壊れない。
テストも「DTOがこの形で返るか」だけ見ればいい。
配列のキー比較を延々やってた頃を思うと、
これが地味にありがたい。
3. nullチェックが減る
DTOを「null非許容」にしておくと、
VMとかControllerでの isset() 祭りがなくなる。
どの値が“空”で、どれが“未設定”かを
フラグや既定値で明示できるようになる。
nullを使うかどうかはチームの方針だけど、
「どっちでもいいけど、決められる」のが大事だなと思いました。
4. “箱”に徹するから混ざらない
DTOはロジックを持たない。
計算とかフォーマットを入れると、責務がぼやける。
// ❌ これはDTOの仕事じゃない
public function formatPrice(): string {
return number_format($this->price) . '円';
}こういうのはViewModelやFormatterに任せて、
DTOは「運ぶだけ」にしておくと整理しやすい。
Laravelの中での立ち位置も見えてきた
Laravelって便利な仕組みが多いけど、
それぞれが似たような役割に見えることもあって、
最初は混乱してました。
| 名前 | 主な役割 |
|---|---|
| Eloquentモデル | DBとのやりとり(保存・更新) |
| Request | 入力の受け取りとバリデーション |
| Resource | APIレスポンスの整形 |
| DTO | 層と層のあいだでデータを安全に渡す |
つまり、DTOは「外部」じゃなくて「内部の構造を整える存在」。
ControllerとUseCase、UseCaseとRepositoryの間とか。
目立たないけど、アプリを支える“仕切り”みたいなもの。
いつ導入すればいいの?
最初から全部に入れる必要はないと思います。
自分の場合は、次のどれかが出てきたときに入れるようにしてます。
- 配列のキー名を3回以上ミスった
- 同じデータを返す処理が複数ある
- ControllerとRepositoryで返り値が違う
- テストで配列の中身を延々assertしてる
これが出始めたら、もう「DTOの出番」です。
自分が落ち着いた運用ルール
- DTO名は
〇〇Row(1件分)と〇〇Result(一覧+メタ情報)で分ける - 変換は
fromRow()の静的メソッドでまとめる - 値はnullを避けて、空文字・0・フラグで表す
- DTOには整形を入れず、ViewModelで完結させる
こう決めてから、迷いが減りました。
「それ、どこに書く?」みたいな思考が減る。
まとめ
DTOって「データを運ぶだけのクラス」だけど、
実はシステム全体の整合性を守る構造なんだなと。
配列でも動くけど、
配列のままだと、後から手を入れるときに
「なにを壊すかわからない」怖さがある。
DTOを挟むだけで、その境界線がはっきりして、
コードが息しやすくなる感じがします。
今回は以上です。