本文へスキップ
BecomeCoder

Spring / Spring Bootコース · 第4章 Web ― REST APIを作る · レッスン15

DTOとリクエストボディ ― @RequestBody

ローカル実施

導入

POSTで新規作成する、PUTで更新する——クライアントからJSONを受け取るAPIも必要です。また、前レッスンの最後で触れた「password までJSONに含まれてしまう」問題への対策も考えます。

説明

リクエストのボディ(JSON)をJavaオブジェクトとして受け取りたいときは、引数に @RequestBody を付けます。

@PostMapping
public User create(@RequestBody CreateUserRequest request) {
    return userService.create(request.getName(), request.getEmail(), request.getPassword());
}

ここで、Entity(データベースのテーブルに対応するクラス。第5章で扱います)をそのままリクエスト/レスポンスに使うのは避けるのが定石です。代わりに、API専用の入れ物である DTO(Data Transfer Object) を用意します。

// リクエスト専用のDTO ― クライアントから受け取る形
class CreateUserRequest {
    private String name;
    private String email;
    private String password; // 受け取りはするが…
}

// レスポンス専用のDTO ― クライアントへ返す形
class UserResponse {
    private Long id;
    private String name;
    private String email;
    // password は含まない!

    public static UserResponse from(User user) {
        return new UserResponse(user.getId(), user.getName(), user.getEmail());
    }
}
graph LR
  Req["JSON(リクエスト)"] -->|@RequestBody| DTOIn["CreateUserRequest"]
  DTOIn --> Service["Service層で処理"]
  Service --> Entity["User(Entity)"]
  Entity --> DTOOut["UserResponse に変換"]
  DTOOut -->|そのまま返す| Res["JSON(レスポンス)"]

DTOを分けることで得られる利点は次の通りです。

  1. 不要な情報を外部に漏らさない … パスワードや内部管理用のフィールドをレスポンスから確実に除外できる。
  2. APIの形とDB構造を独立させられる … Entityにフィールドを足しても、DTOを変えない限りAPIの形は変わらない。
  3. リクエスト用・レスポンス用で必要な項目が違うことを表現できる … 新規作成時だけ必要な password を、レスポンス側の型には持たせない、といった書き分けができる。

「Entityは内部の実装、DTOは外部との約束」——この境界線を意識するのが、実務のSpringアプリでは非常に重要です。

やってみよう

「ユーザーのプロフィール編集API」を作るとして、リクエスト用DTOとレスポンス用DTOにそれぞれどんなフィールドを持たせるべきか考えてみましょう。パスワードの再設定機能がある場合、それは別のAPI(別のDTO)に分けたほうがよいか、あわせて考えてみてください。