SpringBootでAPIを実装する際のリクエスト/レスポンスの操作を学ぶ


## はじめに
最近、SpringBootについて学んでいます。今回はリクエスト、レスポンスをJSONで扱う方法を整理します。

- [はじめに](#はじめに)
- [今回の想定](#今回の想定)
- [controllerの実装](#controllerの実装)
  - [RestController](#restcontroller)
  - [RequestBody](#requestbody)
  - [CalculationRequest / CalculationResponse](#calculationrequest--calculationresponse)
    - [CalculationRequest](#calculationrequest)
    - [CalculationResponse](#calculationresponse)
- [実際のAPIの動き](#実際のapiの動き)
- [リクエストのJSONとクラスの関係](#リクエストのjsonとクラスの関係)
  - [クラスとJSONの比較](#クラスとjsonの比較)
  - [マッピングされない例](#マッピングされない例)
- [いろいろなパターンの実装例](#いろいろなパターンの実装例)
  - [JSONのフィールドにオブジェクト](#jsonのフィールドにオブジェクト)
  - [配列](#配列)
- [バリデーション](#バリデーション)
  - [文字列長チェック](#文字列長チェック)
  - [数値チェック](#数値チェック)
- [まとめ](#まとめ)


## 今回の想定
今回は以下の機能を題材にしてみます。

- request
  - firstNum,secondNumを引数で受け取る
- response
  - 足した結果をresultに設定して返す

##  controllerの実装
まずは実装例から。
それぞれ整理していきましょう。

```java
@RestController
public class CalculationController {

    @PostMapping("/calculate")
    public CalculationResponse calculate(@RequestBody CalculationRequest request) {
        // レスポンスオブジェクトを作成
        CalculationResponse response = new CalculationResponse();

        // 計算処理
        int result = request.getFirstNum() + request.getSecondNum();

        // 結果を設定
        response.setResult(result);

        return response;
    }
}
```
### RestController

RESTの場合は、`@RestController`を使用します。`@Controller`もありますが、こちらはMVCなどで、テンプレート（HTML）を返す場合に使用します。

このアノテーションによって、該当のコントーラーがRESTであることを定義します。

### RequestBody

POSTなどで受け取った情報をクラスと紐付けるために使用します。

`@RequestBody CalculationRequest request`で、Bodyの内容がCalculationRequestに変換されます

### CalculationRequest / CalculationResponse

#### CalculationRequest
リクエストで受け取るクラスを定義します。

```java
public class CalculationRequest {
    private int firstNum;
    private int secondNum;

    // getter, setter
    public int getFirstNum() {
        return firstNum;
    }

    public void setFirstNum(int firstNum) {
        this.firstNum = firstNum;
    }

    public int getSecondNum() {
        return secondNum;
    }

    public void setSecondNum(int secondNum) {
        this.secondNum = secondNum;
    }
}
```

#### CalculationResponse
レスポンスのクラスを定義します。

```java
public class CalculationResponse {
    private int result;

    // getter, setter
    public int getResult() {
        return result;
    }

    public void setResult(int result) {
        this.result = result;
    }
}

```

## 実際のAPIの動き
実装したAPIの動作を確認してみましょう。以下にcurlコマンドを使用したリクエスト例と、それに対するレスポンスを示します。

```bash
curl -X POST http://localhost:8080/calculate \
-H "Content-Type: application/json" \
-d '{
    "firstNum": 10,
    "secondNum": 20
}'
```

- リクエスト例
```json
{
    "firstNum": 10,
    "secondNum": 20
}
```
- レスポンス例
```json
{
    "result": 30
}
```

## リクエストのJSONとクラスの関係
なんとなく伝わったかもしれませんが、`@RequestBody`で指定したクラスにJSONが変換されています。

### クラスとJSONの比較
JSONのキーとクラスのフィールド名が一致する場合、自動的にマッピングされます。

```java
public class CalculationRequest {
    private int firstNum;    // ← JSONの"firstNum"と一致
    private int secondNum;   // ← JSONの"secondNum"と一致
}
```
```json
{
    "firstNum": 10,    // ← クラスのfirstNumフィールドにマッピング
    "secondNum": 20    // ← クラスのsecondNumフィールドにマッピング
}
```


### マッピングされない例

以下のような場合、正しくマッピングされません。

1. フィールド名が異なる場合
```java
public class CalculationRequest {
    private int first;    // "firstNum"とは異なる名前
    private int second;   // "secondNum"とは異なる名前
}
```
```json
{
    "firstNum": 10,    // マッピングされない
    "secondNum": 20    // マッピングされない
}
```

2. データ型が一致しない場合
```java
public class CalculationRequest {
    private String firstNum;    // 文字列型で定義
    private int secondNum;      // 数値型で定義
}
```
```json
{
    "firstNum": 10,     // 数値型のため、String型へのマッピングでエラー
    "secondNum": "20"   // 文字列型のため、int型へのマッピングでエラー
}
```
これらの場合は、`400 Bad Reqest`が発生します。

## いろいろなパターンの実装例
### JSONのフィールドにオブジェクト
オブジェクトをネストすることもできます。

```java
public class UserRequest {
    private String name;
    private Address address;  // オブジェクトをネスト

    // Address クラス
    public static class Address {
        private String city;
        private String street;
        
        // getter, setter
    }
    
    // getter, setter
}
```
```json
{
    "name": "John",
    "address": {
        "city": "Tokyo",
        "street": "Shibuya 1-1-1"
    }
}
```

### 配列
配列やリストも扱えます。

```java
public class BatchCalculationRequest {
    private List<Integer> numbers;  // 数値の配列
    private List<User> users;       // オブジェクトの配列

    // User クラス
    public static class User {
        private String name;
        private int age;
        
        // getter, setter
    }
    
    // getter, setter
}
```
```json
{
    "numbers": [1, 2, 3, 4, 5],
    "users": [
        {"name": "John", "age": 30},
        {"name": "Jane", "age": 25}
    ]
}
```
## バリデーション
バリデーションをすることも可能です。独自実装も可能ですが。こちらもフレームワークにお任せしましょう。

- 必要な依存関係の導入
  - それぞれの環境に合わせて`spring-boot-starter-validation`を導入します。

そして、 controllerで`@Valid`を使用して、バリデーションを有効化します。

```java
@RestController
public class CalculationController {
    @PostMapping("/calculate")
    public CalculationResponse calculate(@Valid @RequestBody CalculationRequest request) {
        // バリデーションエラーの場合、自動的に400エラーが返される
        // ...
    }
}
```

では、それぞれのクラスでバリデーションを実装してみます。

### 文字列長チェック
```java
public class UserRequest {
    @NotNull(message = "名前は必須です")
    @Size(min = 1, max = 50, message = "名前は1～50文字で入力してください")
    private String name;

    @Email(message = "メールアドレスの形式が不正です")
    private String email;
    
    // getter, setter
}
```
### 数値チェック
```java
public class CalculationRequest {
    @Min(value = 0, message = "0以上の値を入力してください")
    @Max(value = 100, message = "100以下の値を入力してください")
    private int firstNum;

    @Positive(message = "正の値を入力してください")
    private int secondNum;
    
    // getter, setter
}
```

他にもいろいろあります。独自のルールでバリデーションを実装することも可能です。


## まとめ
とても基本的なAPIの流れとして、JSONでリクエストを受けて、JSONのレスポンスを返す方法をまとめました。また、バリデーションの実装方法もみてみました。

すごく基本的ですが、API開発で最初に必要になる部分です。
少しでも参考に、とっかかりのハードルが低くなれば幸いです。