Kotlin + Spring BootでREST APIを作ってみる

# はじめに
最近GoogleがAndroidで公式サポートすることを表明したのでやってみました。
**乗るしかない、このビッグウェーブに。**
実はただの備忘録です。

Spring Boot自体案件で使ったことがあっても、構築するのは初めてな上に、
Kotlinの本を読み切る前に手を動かしたため、かなり適当な感じが拭えないです。
指摘があれば是非お願いします。

あと、今回は[Spring Initializr](https://start.spring.io)で作ったひな形を使ってます。

# 使用環境
* macOS 10.12.5
* IntelliJ Idea 2017.1.3
* Java 1.8
* Kotlin 1.1.2
* Spring Boot 1.5.3 RELEASE

# Spring Fox(SwaggerUI)の設定
動作確認にSwaggerUIを使いたいので以下のdependenciesを`build.gradle`に追記。

```build.gradle
// Spring Fox/Swagger
compile "io.springfox:springfox-swagger2:2.6.0"
compile "io.springfox:springfox-swagger-ui:2.6.0"
```

あと、`DemoApplication.kt`と同じ階層に`SwaggerConfiguration.kt`を以下の内容で配置。

```kotlin:SwaggerConfiguration.kt
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import springfox.documentation.swagger2.annotations.EnableSwagger2
import springfox.documentation.builders.RequestHandlerSelectors
import springfox.documentation.spi.DocumentationType
import springfox.documentation.spring.web.plugins.Docket

@EnableSwagger2
@Configuration
class SwaggerConfiguration {

    @Bean
    fun customDocket(): Docket {
        return Docket(DocumentationType.SWAGGER_2).select()
                .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller"))
                .build()
    }

}

```


# Bodyの作成
今回は~~めんどくさいので~~受け取るのも返すのも同じ`Person`とします。

```kotlin:Person.kt

class Person(
        var name: String = "",
        var age: Int = 0
)
```

以下の記述だと`JsonMappingException`でエラーになりました。
エラー読む限りだとデフォルトコンストラクタを拾えてないっぽいので、この書き方だとデフォルトコンストラクタ不可視になっちゃうんですかね。
どっかでそんな記述を見た記憶がありますが…。

```kotlin:Person.kt

// これだとJsonMappingExceptionが発生する。
class Person(
        var name: String,
        var age: Int
)
```

# Controllerの作成
```kotlin:HelloController.kt
import com.example.demo.body.Person
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping(value = "hello")
class HelloController {

    @RequestMapping(value = "/person", method = arrayOf(RequestMethod.POST))
    fun getPerson(@RequestBody person: Person): Person {
        return Person(person.name, person.age)
    }

}

```

# 動作確認
Gradleの`bootRun`タスクを叩いて起動できたら、[SwaggerUI](http://localhost:8080/swagger-ui.html)からリクエストを投げましょう。

# 結果
![FireShot Capture 1 - Swagger UI_ - http___localhost_8080_swagger-ui.ht.jpg](https://qiita-image-store.s3.amazonaws.com/0/69931/7b18742b-e3a1-8d3e-20f4-b511fa9e0f27.jpeg)

# TODO
* `@Range`とかValidate系のアノテーションのメンバへの適用
    * 書き方の問題っぽい
* 省いたところもやってみる
    * SecurityとかDBアクセスとか

# Spring Annotationsを使う

[Stack Overflow](https://stackoverflow.com/questions/36515094/kotlin-and-valid-spring-annotation)に載ってました！
…あれ？なんかKotlinの思想的にはイマイチなのでは…？
とりあえずやったことは以下の通りです。

1. メンバに`@field:<任意のSpring Annotation>`を付与する。
1. 以下のコンストラクタを`constructor`として定義する。
   * パラメータなしのコンストラクタ(デフォルトコンストラクタの代替)
   * パラメータありのコンストラクタ  
※任意。無くても動作します。

## Bodyの修正
```kotlin:Person.kt

import org.hibernate.validator.constraints.Range
import javax.validation.constraints.NotBlank

class Person {
    @field:NotBlank
    var name: String

    @field:Range(min = 18)
    var age: Int

    // 💩💩💩コンストラクタ💩💩💩
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }

    // 💩💩💩デフォルトコンストラクタの代替💩💩💩
    constructor() {
        this.name = ""
        this.age = 0
    }
}
```

## Controllerの修正
引数に`@Valid`を加えるだけです。

```kotlin:HelloController.kt
import com.example.demo.body.Person
import org.springframework.web.bind.annotation.*
import javax.validation.Valid

@RestController
@RequestMapping(value = "hello")
class HelloController {

    @RequestMapping(value = "/person", method = arrayOf(RequestMethod.POST))
    fun getPerson(@Valid @RequestBody person: Person): Person {
        return Person(person.name, person.age)
    }
}
```

## 動作結果2
![FireShot Capture 002 - Swagger UI_ - http___localhost_8080_swagger-ui.h.jpg](https://qiita-image-store.s3.amazonaws.com/0/69931/cda6098f-22c1-4f33-1b25-945c077e8765.jpeg)

```json:Response.json

{
  "timestamp": 1495557375614,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.bind.MethodArgumentNotValidException",
  "errors": [
    {
      "codes": [
        "NotBlank.person.name",
        "NotBlank.name",
        "NotBlank.java.lang.String",
        "NotBlank"
      ],
      "arguments": [
        {
          "codes": [
            "person.name",
            "name"
          ],
          "arguments": null,
          "defaultMessage": "name",
          "code": "name"
        }
      ],
      "defaultMessage": "may not be empty",
      "objectName": "person",
      "field": "name",
      "rejectedValue": "",
      "bindingFailure": false,
      "code": "NotBlank"
    },
    {
      "codes": [
        "Range.person.age",
        "Range.age",
        "Range.int",
        "Range"
      ],
      "arguments": [
        {
          "codes": [
            "person.age",
            "age"
          ],
          "arguments": null,
          "defaultMessage": "age",
          "code": "age"
        },
        9223372036854776000,
        18
      ],
      "defaultMessage": "must be between 18 and 9223372036854775807",
      "objectName": "person",
      "field": "age",
      "rejectedValue": 17,
      "bindingFailure": false,
      "code": "Range"
    }
  ],
  "message": "Validation failed for object='person'. Error count: 2",
  "path": "/hello/person"
}

```

# まとめ
Kotlinでも当然ながらSpringの基本機能は問題なく使えそうです。
ただ、アノテーション周りはいまいちな感じがするので、このあたりはもう少し調べていきたいですね。
あと残りのTODOも。