Spring BootのFormクラスとDTOの違いを解説する記事のアイキャッチ。若手エンジニアがJava道場風の学習空間でクラスの役割分担を学ぶイラスト。

Spring

Spring BootのFormクラスとDTOの違いは?混ぜると困る理由を解説

AIによる要約

Formクラスは画面やリクエストから入力値を受け取るためのクラス、DTOは層の間でデータを受け渡すためのクラスとして使われます。現場では名前だけでなく、どの層の都合を表すクラスなのかを分けることが重要です。

新人SE
新人SE
FormクラスもDTOも値を入れるだけなら、同じクラスでよくないですか?
ポンコツSE
ポンコツSE
小さい画面なら一時的に動きます。ただ、画面都合と業務処理都合が混ざると、修正時に影響範囲が広がります。現場ではここがレビューされます。

Spring Bootの業務システムでは、Form、DTO、Entityのような値を入れるクラスがたくさん出てきます。

初学者にとっては、どれもフィールドとgetter/setterを持つ似たクラスに見えます。しかし、現場では役割を混ぜると後から修正しづらくなります。

この記事のポイント

  • Formクラスは画面やリクエストの入力都合を表す
  • DTOは層の間のデータ受け渡しに使う
  • Entityを画面入力に直接使うのは避ける
  • 名前ではなく、どの層の都合を表すかで判断する

Formクラスは画面入力の都合を表す

Formクラスは、画面やリクエストから入力された値を受け取るためのクラスです。画面の入力欄、チェックボックス、セレクトボックスなどと対応しやすい形になります。

public class UserRegisterForm {
    @NotBlank
    private String name;

    @NotBlank
    @Email
    private String email;

    private String departmentCode;
}

Formクラスには、画面入力として必要な制約や、未入力時の扱いが入ります。つまり、画面側の事情を受け止めるクラスです。

DTOは層の間の受け渡しに使う

DTOは、Data Transfer Objectの略で、データを受け渡すためのクラスです。Serviceの入力、Controllerへのレスポンス、外部APIとのやり取りなど、使われる場所はプロジェクトによって変わります。

public class UserRegisterCommand {
    private final String name;
    private final String email;
    private final String departmentCode;

    public UserRegisterCommand(String name, String email, String departmentCode) {
        this.name = name;
        this.email = email;
        this.departmentCode = departmentCode;
    }
}

この例では、画面入力のFormをそのままServiceへ渡すのではなく、業務処理に必要な形へ変換しています。プロジェクトによってはCommand、Request、DTOなど名前は違います。

同じクラスで済ませると何が困るか

小さい機能では、FormもDTOも同じに見えます。しかし、画面の都合と業務処理の都合はズレることがあります。

// 画面では文字列で受けたい
private String birthDate;

// Serviceでは日付として扱いたい
private LocalDate birthDate;

画面では未入力や表示形式の都合で文字列にしたい一方、ServiceではLocalDateとして扱いたいことがあります。同じクラスにすると、どちらかの都合を無理に押し込むことになります。

EntityをForm代わりにしない

新人がやりがちな危険な実装は、Entityをそのまま画面フォームに使うことです。

@PostMapping("/users")
public String register(@Valid User user) {
    userRepository.save(user);
    return "redirect:/users";
}

これは簡単に見えますが、画面で入力させたくない項目まで更新対象になったり、DB構造が画面に漏れたりします。現場では避けることが多いです。

変換処理はどこに置くか

FormからDTOやCommandへ変換する場所は、プロジェクト方針によります。小さい機能ならControllerで変換してもよいですが、変換が複雑なら専用メソッドを用意します。

@PostMapping("/users")
public String register(@Valid UserRegisterForm form,
                       BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return "users/new";
    }

    UserRegisterCommand command = form.toCommand();
    userService.register(command);
    return "redirect:/users";
}

ここで大事なのは、Serviceが画面の細かい都合を知りすぎないことです。Serviceには、業務処理に必要な形の値を渡すと読みやすくなります。

現場レビューではどう見られるか

// レビューコメント例
EntityをFormとして使うのは避けたいです。
画面入力用のFormクラスを作って、Serviceに渡す形へ変換してください。

// レビューコメント例
このDTOは画面入力用なのか、Serviceへ渡すためなのか役割が曖昧です。
名前か置き場所で意図が分かるようにしましょう。

レビュー前のセルフチェック

確認リスト

  • Formクラスに画面入力以外の責務を入れていないか
  • DTOがどの層の受け渡し用か名前から分かるか
  • Entityをそのまま画面入力に使っていないか
  • FormからService用の値へ変換する場所が説明できるか

この記事とあわせて読みたい

まとめ

Formクラスは画面入力の都合を受けるクラス、DTOは層の間でデータを受け渡すクラスとして考えると整理しやすいです。

新人のうちは、クラスの中身だけでなく、どの層の都合を表しているのかを見る癖を付けると、Spring Bootの現場コードを読みやすくなります。

-Spring
-, , , , ,