Kotlin+Spark FrameworkでAPI作ってみた

#備忘録
*自分に向けて書いた備忘録
変なところが多々あると思うのでご指摘お願いします。

#プロジェクト作成

今回はあえて[Spring Initializr](https://start.spring.io/)を使ってプロジェクトの雛形を作ってもらいます
`Gradle　Project with Kotlin`にして作成．

##プロジェクトを開く
作成したプロジェクトを開きます
なんでもいいですが[intelliJ](https://www.jetbrains.com/idea/)がオススメですね!
学生は有料版も無料で使えるみたいです

##gradleファイル設定

build.gradleにsparkの依存関係を記述します。sqlとORMもすでに記述しています。
多分余計なものたくさんあります・・・
このファイル内のspring関係は削除しちゃってください笑
元あるものを全消ししてこれを貼り付ける時、import org.~~.classpathみたいなのが出てきたらOKをする
これをしないとクラスパスがうまく設定できなくて後々実行できなくなります。

```:build.gradle
buildscript {
	ext {
		kotlinVersion = '1.2.10'
		springBootVersion = '1.5.9.RELEASE'
	}
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
		classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
		classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
	}
}

apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
compileKotlin {
	kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
	kotlinOptions.jvmTarget = "1.8"
}

repositories {
	mavenCentral()
	maven {
			url('https://dl.bintray.com/kotlin/exposed/')
	}
}


dependencies {
	compile('org.springframework.boot:spring-boot-starter-web')
	compile("org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}")
	compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
	testCompile('org.springframework.boot:spring-boot-starter-test')
	compile 'mysql:mysql-connector-java:5.1.6'
	compile 'com.sparkjava:spark-core:2.6.0'
	runtime "org.jetbrains.kotlin:kotlin-reflect:0.13.1513"
	runtime group: 'org.jetbrains.exposed', name: 'exposed', version: '0.8.5'
	compile group: 'org.jetbrains.exposed', name: 'spring-transaction', version: '0.8.5'
}
```
`src/main/kotlin/com.example.demo`の中にあるDemoApplication.ktをいじります

```kotlin:DemoApplication.kt
package com.example.demo

import spark.Spark.*

fun main(args: Array<String>) {
    get("/hello"){ _, _->
        "hello world"
    }

}
```

簡単にhello worldしましょう。
初期状態ではspringプロジェクトを起動しようとするので、Demo~~.ktの画面で右クリックでDebugかRunしてくれれば起動できます。

起動できたらhttp://localhost:4567/hello でhello world完了です。

#データベースを取り入れる

##テーブルの作成

サンプルとしてローカルのdbにuserテーブルを作成して、そのテーブルを操作します。

```kotlin:user.kt
package model

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.*


object User_t : Table("users") {
    val id = integer("id").autoIncrement().primaryKey()
    val name = varchar("name", 50).uniqueIndex()
}

data class User(
    var id : Int = 0,
    var name : String? = null
  )

```



kotlinのORM,[exposed](https://github.com/JetBrains/Exposed)を使います
まずはローカルのdbにコネクトしuserテーブルをcreateします
今回はmysqlを使っているので、sqlで`create database [db名]`でデータベースを作成します．

```kotlin:connection.kt
package db

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.SchemaUtils.create
import model.*

fun DBconnect(){
    Database.connect("jdbc:mysql://localhost/[DB名]", "com.mysql.jdbc.Driver","ユーザー名","パスワード")
    transaction {
      create(User_t)
    }
}
```

Demo~.ktにconnectを追加します。

```kotlin:DemoApplication.kt
package com.example.demo

import spark.Spark.*
import db.DBconnect

fun main(args: Array<String>) {
    DBconnect()
    get("/hello"){ _, _->
        "hello world"
    }

}
```

これで実行すればdbにusersテーブルが作成されているはず・・・！


## データを追加する
今回はhttpのリクエストボディから名前を取り出しusersテーブルに格納していこうと思います。
レスポンスをjsonに直すのに以下のファイルが必要になるので追加します。

```kotlin:JsonTransformer.kt
package com.example.demo

import com.fasterxml.jackson.databind.ObjectMapper
import spark.ResponseTransformer

class JsonTransformer(private val objectMapper: ObjectMapper) : ResponseTransformer {

    override fun render(model: Any?): String =
            objectMapper.writeValueAsString(model)
}
```
以下をmainの中に記述してください。DBconnectの後あたりに・・・

```kotlin:DemoApplication.kt
    path("/users"){
        post("", UserController().addUser(),JsonTransformer(ObjectMapper().registerKotlinModule()))
    }
```
リクエストを処理したいのでcontrollerを以下の様に用意

```kotlin:userController.kt
package controller

import spark.*
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper


class UserController {
    fun addUser(): Route = Route { req, _ ->
        model.AddUser(jacksonObjectMapper().readValue(req.body()))
    }
}
```
マッパーがリクエストボディから値を取り出してマッピングしてくれるので、そのままmodelの関数にぶち込みます。
`user.kt`に以下を追加して実行してみます。

```kotlin:user.kt
fun AddUser(user: User): User{
    transaction {
        user.id = User_t.insert {
            it[User_t.name] = user.name
        } get User_t.id
    }
    return user
}
```

postmanやらなんやらを使ってpostするとjsonが返ってきます。
content-typeを指定しないとjsonだと認識してくれませんけど・・・そこは割愛
最後にgithubあげるんでそこをみてもらえるとよいです。

##データを取得する

paramにidを指定したユーザ取得と、全ユーザの取得を行います。

```kotlin:DemoApplication.kt
    path("/users"){
        post("", UserController().addUser(), JsonTransformer(ObjectMapper().registerKotlinModule()))
        get("/:id",UserController().getUser(),JsonTransformer(ObjectMapper().registerKotlinModule()))
        get("",UserController().getUserList(),JsonTransformer(ObjectMapper().registerKotlinModule()))
    }
```

```kotlin:user.kt
fun GetUser(id : Int): User{
    lateinit var user : User
    transaction {
        User_t.select {
            User_t.id.eq(id)
        }.forEach {
            user = User(it[User_t.id],it[User_t.name])
        }
    }
    return user
}

fun GetUserList():MutableList<User>{
    lateinit var user : User
    val userList : MutableList<User> = mutableListOf()
    transaction {
        User_t.selectAll().forEach {
            user = User(it[User_t.id],it[User_t.name])
            userList += user
        }
    }
    return userList
}

```

```kotlin:userController.kt
    fun getUser(): Route = Route { req, _ ->
        model.GetUser(req.params("id").toInt())
    }
    fun getUserList(): Route = Route { _, _ ->
        model.GetUserList()
    }
```

それぞれこんな感じで追加すればいけます。

http://localhost:4567/users/2 は
`{"id":1,"name":"sample1"}`
http://localhost:4567/users は
`[{"id":1,"name":"sample1"},{"id":2,"name":"サンプル２"}]`
こんな感じで取れれば成功です。

ソースコードはこちらに上げています。
https://github.com/mr04vv/kotlin_spark_demo

#exposedについて
joinしたい時は、テーブル定義の箇所を以下の様にします。

```kotlin
object GroupMember_t : Table("group_members") {
    val id = integer("id").autoIncrement().primaryKey()
    val group_id = integer("group_id")
    val user_id = (integer("user_id") references User_t.id)
}
```
`references User_t.id`という依存関係を明記して上げないとjoinできません・・・
joinの例はこんな感じ

```kotlin
  transaction{
    (GroupMember_t innerJoin User_t).slice(User_t.id,User_t.name).
      select{
        GroupMember_t.group_id.eq(group)
      }.forEach{
        user = GroupMember(it[User_t.id],it[User_t.name])
        users += user
      }
  }
```

exposedのドキュメントがほとんど無くて結構躓きました・・・