導入
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を分けることで得られる利点は次の通りです。
- 不要な情報を外部に漏らさない … パスワードや内部管理用のフィールドをレスポンスから確実に除外できる。
- APIの形とDB構造を独立させられる … Entityにフィールドを足しても、DTOを変えない限りAPIの形は変わらない。
- リクエスト用・レスポンス用で必要な項目が違うことを表現できる … 新規作成時だけ必要な
passwordを、レスポンス側の型には持たせない、といった書き分けができる。
「Entityは内部の実装、DTOは外部との約束」——この境界線を意識するのが、実務のSpringアプリでは非常に重要です。
やってみよう
「ユーザーのプロフィール編集API」を作るとして、リクエスト用DTOとレスポンス用DTOにそれぞれどんなフィールドを持たせるべきか考えてみましょう。パスワードの再設定機能がある場合、それは別のAPI(別のDTO)に分けたほうがよいか、あわせて考えてみてください。