サーバサイドKotlinのボイラープレート開発で思ったこと

## はじめに
お久しぶりです。[ｋ．ｓ．ロジャース](https://www.wantedly.com/companies/ks-rogers)の西谷です。

最近はKotlinを使ったREST APIサーバのボイラープレートを設計及び開発を行いました。
リリースが無事完了し、弊社の開発に導入されています。
今回はサーバサイドKotlinを使ってみて思ったことや困ったことを共有できたらと思います。

間違い等があればコメントにて教えて頂けたらと思います。

## 環境など
今回は以下の構成で実装しました。

- Kotlin 1.3.61
- SprintBoot 2.2.4.RELEASE
- Exposed 0.21.1
- Mysql 8.0.19

Webフレームワークは選択肢として`SpringBoot`と`Ktor`がありましたが、`SpringBoot`の実績や安定性を考慮して`SpringBoot`を使いました。


## 作ったもの
DDDベースの設計を行いました。
具体的なディレクトリ構成は次の通りです。

```
.
├── kotlin
│   └── com
│       └── ksrogers
│           ├── application
│           │   ├── common
│           │   │   ├── annotation
│           │   │   └── error
│           │   │       ├── exception
│           │   │       └── handler
│           │   ├── controller
│           │   ├── middleware
│           │   │   ├── advice
│           │   │   ├── filter
│           │   │   └── interceptor
│           │   ├── request
│           │   └── response
│           ├── config
│           ├── domain
│           │   ├── model
│           │   ├── repository
│           │   └── service
│           ├── infrastructure
│           │   ├── table
│           │   ├── mysql
│           │   └── repository
│           └── util
└── resources
    ├── db
    │   └── migration
    └── i18n
        ├── common
        └── errors
```

中心となる層の実装内容は以下の通りで、詳細なDDDアーキテクチャの解説は他記事にお任せしたいと思います。

- application
    - controller: APIのエンドポイントと処理
    - middleware: controller前の共通処理
    - request: リクエスト
    - response: レスポンス
- infrastructure
    - table: DBのテーブル構造
    - repository: DBのレコード操作
- domain
    - model: ドメインモデル
    - repository: repositoryのインターフェイス定義
    - service: ドメインサービス



## サーバサイドKotlinの良かったところ
### コードが直感的
Kotlin独特の書き方は少ない印象でした。
過度な省略記法や暗黙的な呼び出しもなく、必要に応じてコードを追いかけることが容易でした。

このことから複数の言語を触ったことのある方でしたら気軽にKotlinに移ることができると思います。

### 言語機能が豊富
Collection, Generics, Extensionなどの機能が揃っており、実装したい機能に対して「あの言語のこれがあれば」みたいなことは無かった印象です。
また、ライブラリ関連もJavaのものが使えるため困ることがありませんでした。

### 解説記事が多い
Kotlinの解説記事は少ないですが、Javaに関する解説記事は大量にあります。
特にSpringBootを使う場合は、困ったときにJavaの記事から調査ができるため手詰まりになることは少ないです。
InteliJを使っている場合はJavaのコードをKotlinに自動変換する機能もあり、そのまま動いたりすることもあります。

## サーバサイドKotlinで困ったところ
Kotlinは凄くいい言語と感じましたが、SpringBootの方は落とし穴が結構ありました。
他言語から来た場合は学習コストが高いかもしれません。

### RequestのValidationは全てnullableで行う必要がある
SpringBootはRequestの定義とValidationを同時に行います。
具体的には以下のようなクラスを定義します。

```kotlin
data class SampleRequest(
  @field:NotNull
  val title: String?,
  val other: String?
```
上記のサンプルの場合、`title`はNon-NullにもかかわらずNullableで定義する必要があります。
(Nullableで設定しない場合はNullが飛んできたときに例外が発生します)

Non-Nullにも関わらず毎回nullチェックが必要になり非常に煩雑です。
これを回避するために以下のように実装しました。

```kotlin
data class SampleRequest(
  @JsonProperty("title")
  @field:NotNull
  private val _title: String?,
  val other: String?
) {
  val title: String
    get() = _title ?: ""
}
```

### リクエストの前処理が大掛かりになる
通常のWebフレームワークであれば、ControllerのBaseクラスに`beforeExecuteXXX`を書いて終わりと思っていました。
しかし、SpringBootは複雑で幾つもの割り込みのタイミングがあります。
[こちら](https://qiita.com/kazuki43zoo/items/757b557c05f548c6c5db)の記事に詳細が纏められており、参考にさせて頂きました。

### RequestBodyをコントローラに渡す前にメタデータを付与したい
リクエストの生データ自体は`request.getInputStream()`で取得することができます。
しかし、これを使ってしまうとメソッド名の通りstreamとして取得してしまうため、Controllerにデータが流れなくなります。

こちらに関しては以下のようにoverrideすることで解決できました。


```kotlin
class RequestControllerAdvice : RequestBodyAdvice {
  override fun beforeBodyRead(
    inputMessage: HttpInputMessage,
    parameter: MethodParameter,
    targetType: Type,
    converterType: Class<out HttpMessageConverter<*>>
  ): HttpInputMessage {
    val body = IOUtils.toString(inputMessage.body)

    val builder = StringBuilder()
    builder.append(body)

    val parser = Parser.default()
    val json = parser.parse(builder) as JsonObject

    // do something
    json["_meta"] = "hogehoge"

    val returnInputStream: InputStream = json.toJsonString().byteInputStream()
    val returnHeaders: HttpHeaders = inputMessage.headers

    return object : HttpInputMessage {
      override fun getBody(): InputStream = returnInputStream
      override fun getHeaders(): HttpHeaders = returnHeaders
    }
  }
}
```

### マイグレーションツールがない
Javaのマイグレーションは`Flyway`が主流です。
しかし、これはバージョンに沿ってSQLを実行するだけで機能として不十分な印象があります。
モデルベースの操作やロールバックなども必要だと思いました。

今後のExposedに期待したいです。



## おわりに
今回はサーバサイドKotlinを触ってみた感想について共有させて頂きました。
今後はKtorの方も触ってみようと思います。

間違い等あればご指摘頂けたらと思います。

### Wantedlyでもブログ投稿してます
Techブログに加えて会社ブログなどもやっているので、気になった方はぜひ覗いてみてください。
https://www.wantedly.com/companies/ks-rogers