KotlinでSpring Boot2

サーバサイドKotlin、流行ってますね！！（？）
Kotlin用フレームワークは黎明期なので、Javaなフレームワークを選択する事になると思います。
Javaフレームワークと言ったらSpringFrameworkですよね。
KotlinでSpringBoot2をやってみたです。

サンプルでDBからデータと取得し返すRestAPIを作成します。

## Projectを作成する
https://start.spring.io/
Spring InitializrでProjectを作成します。
![Spring Initializr.png](https://qiita-image-store.s3.amazonaws.com/0/36892/064a6aed-7ba4-ea33-882f-68917e195de6.png)

DLしたプロジェクトをIntelliJ IDEAなどで開きます。

お好みですが、APサーバをTomcatではなく、Jettyとかを使用したいときは、dependenciesにJettyを追加し、spring-boot-starter-webに含まれているTomcatを除外します。

```gradle:build.gradle
implementation('org.springframework.boot:spring-boot-starter-jetty')
```
```gradle:build.gradle
configurations {
    implementation.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
```

jdbcを入れているのでDriverが起動時に必要です。dependenciesに追加します。

```gradle:build.gradle
runtime('com.h2database:h2')
```

これもお好みですが、application.propertiesではなくapplication.yml使用したい場合は、　application.propertiesを削除してapplication.ymlを作成します。

```yaml:application.yml
## 例です
logging:
  level:
    root: warn
    org:
      springframework:
        web: info
spring:
  main:
    banner-mode: "off"
```

## Controllerの作成
まず、簡易なAPIを作成します。
Annotationも気にせずに使用できます。

```Kotlin:DemoRestController.kt
import com.fasterxml.jackson.annotation.JsonProperty
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping(value = ["/api"])
class DemoRestController {

	@GetMapping(
			value = ["/"],
			produces = [MediaType.APPLICATION_JSON_VALUE]
			)
	fun index(): IndexJson {
		return IndexJson("hogehoge", 12345)
	}
}

data class IndexJson(
		@JsonProperty(value = "name") val name: String,
		@JsonProperty(value = "no") val no: Int
)
```
Data Classを、そのまま返すだけなのでお手軽ですね。

### 起動
```bash
$ ./gradlew bootRun
```
```bash
$ curl -D - http://localhost:8080/api/
HTTP/1.1 200 OK
Date: Wed, 19 Dec 2018 10:30:50 GMT
Content-Type: application/json;charset=utf-8
Transfer-Encoding: chunked

{"name":"hogehoge","no":12345}
```

はい。Jsonで返ってきました。

## DB接続
H2の定義を追加します。

```yaml:application.yml
spring:
  main:
    banner-mode: "off"
  datasource:
    url: jdbc:h2:/tmp/h2/demo
    username: sa
    password:
  h2:
    console:
      enabled: true
```

アプリケーションを再起動すればH2のWebコンソールが起動します。
「 http://localhost:8080/h2-console/ 」でコンソールを参照できます。

![スクリーンショット 2018-12-19 20.08.08.png](https://qiita-image-store.s3.amazonaws.com/0/36892/66219f4f-0c16-f216-49fc-b796da5b6231.png)

![スクリーンショット 2018-12-19 22.16.17.png](https://qiita-image-store.s3.amazonaws.com/0/36892/480361bf-0019-1a80-fcf0-e63f62199fd2.png)

ログインするとSQLを実行できる画面が表示されるので、適当にテーブルとデータを作成、登録してください。

```sql
create table test (
  name varchar2(10),
  no int
);

insert into test values ('hoge1', 10);
insert into test values ('hoge2', 20);
insert into test values ('hoge3', 30);
```

### Repository、Service
作成テーブルからデータを取得するRepositoryとServiceを作成します。

```kotlin:User.kt
data class User(val name: String, val no:Int)
```
```kotlin:DemoRepository.kt
interface DemoRepository {
	fun all(): List<User>
	fun findByName(name: String): User
}
```
```kotlin:JdbcDemoRepository.kt
import com.example.demo.repository.DemoRepository
import com.example.demo.repository.User
import org.springframework.jdbc.core.RowMapper
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.stereotype.Repository

@Repository
class JdbcDemoRepository(private val jdbcTemplate: NamedParameterJdbcTemplate) : DemoRepository {

	private val rowMapper = RowMapper<User> { rs, _ ->
		User(
				rs.getString("name"),
				rs.getInt("no")
		)
	}

	override fun all(): List<User> =
		jdbcTemplate.query("select name, no from test", rowMapper)

	override fun findByName(name: String): User {
		val res = jdbcTemplate.query(
				"select name, no from test where name = :targetName",
				MapSqlParameterSource().addValue("targetName", name),
				rowMapper
		)

		return res.requireNoNulls()[0]
	}
}
```
```kotlin:DemoService.kt
import com.example.demo.repository.User

interface DemoService {
	fun findUser(name: String): User
}
```
```kotlin:DemoServiceImpl.kt
import com.example.demo.repository.DemoRepository
import com.example.demo.repository.User
import com.example.demo.service.DemoService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class DemoServiceImpl(@Autowired var repository: DemoRepository): DemoService {
	override fun findUser(name: String): User = repository.findByName(name)
}
```

最初に作成したControllerを修正します。

```kotlin:DemoRestController.kt
import com.example.demo.service.DemoService
import com.fasterxml.jackson.annotation.JsonProperty
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import javax.validation.Valid
import javax.validation.constraints.NotBlank

@Validated
@RestController
@RequestMapping(value = ["/api"])
class DemoRestController {

	@Autowired
	lateinit var service: DemoService

	@GetMapping(
			value = ["/{name}"],
			produces = [MediaType.APPLICATION_JSON_VALUE]
			)
	fun index(@PathVariable("name") @Valid @NotBlank userName: String): IndexJson {
		val user= service.findUser(userName)
		return IndexJson(user.name, user.no)
	}
}

data class IndexJson(
		@JsonProperty(value = "name") val name: String,
		@JsonProperty(value = "no") val no: Int
)
```

再起動してAPIを呼んでみます。

```bash
$ curl http://localhost:8080/api/hoge1
{"name":"hoge1","no":10}
$ curl http://localhost:8080/api/hoge2
{"name":"hoge2","no":20}
```

## 所感
ハマり所は、ほぼありませんでした。
SpringといえばDIですが(?)、プロパティ宣言部分をKotlinの場合は、どう書くかで悩んだのと、
コンストラクタの書き方が慣れなかったくらいでした。

これからもKotlinを使っていこうと思いが強くなりました。
Let's Enjoy Kotlin！！