Kotlin
Spark
exposed

Kotlin+Spark FrameworkでAPI作ってみた

備忘録

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

プロジェクト作成

今回はあえてSpring Initializrを使ってプロジェクトの雛形を作ってもらいます
Gradle Project with Kotlinにして作成.

プロジェクトを開く

作成したプロジェクトを開きます
なんでもいいですがintelliJがオススメですね!
学生は有料版も無料で使えるみたいです

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をいじります

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テーブルを作成して、そのテーブルを操作します。

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を使います
まずはローカルのdbにコネクトしuserテーブルをcreateします
今回はmysqlを使っているので、sqlでcreate database [db名]でデータベースを作成します.

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を追加します。

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に直すのに以下のファイルが必要になるので追加します。

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の後あたりに・・・

DemoApplication.kt
    path("/users"){
        post("", UserController().addUser(),JsonTransformer(ObjectMapper().registerKotlinModule()))
    }

リクエストを処理したいのでcontrollerを以下の様に用意

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に以下を追加して実行してみます。

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を指定したユーザ取得と、全ユーザの取得を行います。

DemoApplication.kt
    path("/users"){
        post("", UserController().addUser(), JsonTransformer(ObjectMapper().registerKotlinModule()))
        get("/:id",UserController().getUser(),JsonTransformer(ObjectMapper().registerKotlinModule()))
        get("",UserController().getUserList(),JsonTransformer(ObjectMapper().registerKotlinModule()))
    }
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
}

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":"サンプル2"}]
こんな感じで取れれば成功です。

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

exposedについて

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

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の例はこんな感じ

  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のドキュメントがほとんど無くて結構躓きました・・・