【Kotlin】SpringBoot/JPAのはじめ方2 - Controller編

こんにちは。こまつです。

はじめに
---
https://qiita.com/komatsuh/items/a8a81bc889c4cf88d9c5

これの続きです。


前提
---
◾️REST送受信のツールが必要
API開発時にはPOSTMAN等のツールをインストールしておく必要があります。

◾️DIとは
ざっくりいうと、@Component, @RestController, @Repositoryなどのクラスアノテーションをつけておくと、
Springがオブジェクトをnewしてくれます。シングルトンなので状態は持てません。
自分でnewさえしなければテスト時に別のオブジェクトに差し替えたりできます。

コントローラを作成する
---
コントローラの1メソッド(Mapping)がそれぞれAPIになります。
RESTではCRUDをHTTPのメソッドに対応させて、keyをPATHパラメータで送信するのが王道かと思います。

* 作成 = [POSTメソッド] /users/  -> 1件作成される(内容はリクエストBodyで送信)
* 取得 = [GETメソッド] /users/1  -> 指定されたID[1]が取得される
* 更新 = [PUTメソッド] /users/1  -> 指定されたID[1]が更新される(内容はリクエストBodyで送信)
* 削除 = [DELETEメソッド] /users/1  -> 指定されたID[1]が削除される
* リスト = [GETメソッド] /users/  -> ユーザが全件取得される

```UserController.kt
@RestController             <- コントローラである目印
@RequestMapping("/users")   <- 以後のマッピングは/users/配下にマッピングされる
class UserController(private val userRepository: UserRepository) { <- DIされたモジュールがコンストラクタで渡される

    @PostMapping("/")       <- /users/に対するPOST
    fun create(@RequestBody request: UserPostRequest): User {
        return userRepository.save(request.toEntity())  <- ここではEntityを直接返却している。IDは採番される
    }

    @GetMapping("/{id}")    <- /users/ユーザID に対するGet
    fun read(@PathVariable id: Int): User {
        return userRepository.findById(id)               <- 指定されたIDを検索して見つかったEntityを返却する
            .orElseThrow { NotFoundException("ない") }    <- 存在しなかったら404
    }

    @PutMapping("/{id}")    <- /users/ユーザID に対するPUT
    fun update(@PathVariable id: Int, @RequestBody request: UserPutRequest): User { <- 更新後に更新済みEntityを返却する
        return userRepository.findById(id)                                 <- 存在したら
            .map { user -> userRepository.save(request.toEntity(user)) }   <- 更新する
            .orElseThrow { NotFoundException("ない") }                      <- 存在しなかったら404
    }

    @DeleteMapping("/{id}")  <- /users/ユーザIDに対するDELETE
    fun delete(@PathVariable id: Int) {
        userRepository.deleteById(id)       <- 指定されたIDのユーザをDBから削除する
    }

    @GetMapping("/") <- /users/に対するGET
    fun list(): List<User> { 
        return userRepository.findAll();   <- ユーザのリスト(全件)をDBから取得する
    }
}
```

リクエストDTO(フォーム)
---
Entity生成メソッドを持たせています。
DTOはEntityに依存していいですが逆はだめです。

```UserRequest.kt
// ユーザ作成リクエスト
data class UserPostRequest(val name: String) {
    fun toEntity(): User {
        return User(null, name)          <- RequestDtoはEntityを参照してよい。POSTならuserId=nullでEntityを生成する
    }
}

// 車更新リクエスト
data class UserPutRequest(val name: String) {
    fun toEntity(user: User): User {
        return User(user.id, name)       <- <- Putならuser受信したUserIdをEntityに設定する
    }
}
```


例外クラス
---
404を表現するクラスを作成します。
存在しないPathにリクエストされるとSpringが自動で404を応答します。

```NotFoundException.kt
@ResponseStatus(HttpStatus.NOT_FOUND)   <- HTTP応答を指定する(404)
class NotFoundException(msg: String) : RuntimeException(msg)   
```


動かしてみる
---
起動すると localhost:8080 で待ち受けます。
作成したAPIのPathに対応するリクエストを送信します。

◾️ユーザ作成(POST)
![スクリーンショット 2022-09-25 11.08.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1342736/ce087944-1519-4a35-7fb0-822b9ec5dd8c.png)

◾️ユーザ作成のPOST結果
![スクリーンショット 2022-09-25 11.08.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1342736/827a38a9-0b51-9f87-5229-7456569b313a.png)

◾️Userテーブルの状態
Postしたデータが登録されています
POSTの結果(200![スクリーンショット 2022-09-25 11.09.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1342736/e62556b8-a237-1e49-a0d3-abcad2a0f24d.png)

◾️Userリストを取得
![スクリーンショット 2022-09-25 11.14.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1342736/f68c0909-3d09-385e-27ef-0ecd1628082c.png)

他のAPIも問題なく動作しているようです。


まとめ
---
ここまでで、Http <-> APIサーバ <-> DB までのCRUD+Listが作成できました。

◾️Controllerはリクエストを特定のPathにマッピングするクラス
◾️RESTのURI設計はどこかのベスプラにしたがう
◾️404とか401は例外クラスを作っておいてそれで返す

思っていること
---
フロント開発や、動的言語に慣れている人はJavaのホットリロードに不満を感じることが多いようですが、これは静的言語＆サーバーサイドの開発スタイルの違いに慣れていないのが原因です。

ホットリロード(結果を見ながら開発)をしたい動機は以下と思います。
・フロント=結局見ないと正しいかわからない
・動的言語=実行時エラーがでる

サーバ開発では返却値は型で定義されていますし、型安全に作れば実行時エラーはほぼなくなりますので、上記の動機がないです。
なのでSQLレス&型安全に作って実行時例外の発生ポイントを少なくし、一発で動かす開発スタイルがいいと思います。
その上で、動作を確認したければステップ実行、仕様的な検証をしたければJUnitなどが用意されています。