DTOって何?なんで必要?

2025.10.07 09:00
2025.10.21 20:46
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入力の受け取りとバリデーション
ResourceAPIレスポンスの整形
DTO層と層のあいだでデータを安全に渡す

つまり、DTOは「外部」じゃなくて「内部の構造を整える存在」。
ControllerとUseCase、UseCaseとRepositoryの間とか。
目立たないけど、アプリを支える“仕切り”みたいなもの。

いつ導入すればいいの?

最初から全部に入れる必要はないと思います。
自分の場合は、次のどれかが出てきたときに入れるようにしてます。

  • 配列のキー名を3回以上ミスった
  • 同じデータを返す処理が複数ある
  • ControllerとRepositoryで返り値が違う
  • テストで配列の中身を延々assertしてる

これが出始めたら、もう「DTOの出番」です。

自分が落ち着いた運用ルール

  • DTO名は 〇〇Row(1件分)と 〇〇Result(一覧+メタ情報)で分ける
  • 変換は fromRow() の静的メソッドでまとめる
  • 値はnullを避けて、空文字・0・フラグで表す
  • DTOには整形を入れず、ViewModelで完結させる

こう決めてから、迷いが減りました。
「それ、どこに書く?」みたいな思考が減る。

まとめ

DTOって「データを運ぶだけのクラス」だけど、
実はシステム全体の整合性を守る構造なんだなと。

配列でも動くけど、
配列のままだと、後から手を入れるときに
「なにを壊すかわからない」怖さがある。

DTOを挟むだけで、その境界線がはっきりして、
コードが息しやすくなる感じがします。

今回は以上です。