Ktor+Gradle Kotlin DSL+ MoshiでAPIを書いてみた

##Ktorとは
>Ktor is a framework for building asynchronous servers and clients in connected systems using the powerful Kotlin programming language. This website provides a complete reference to the Ktor application structure and programming interface. And how to approach particular tasks.

「Kotlin製の軽量なWebアプリケーションフレームワーク」とのことで今回1.0.0正式版がでたので[How to create an API using ktor](https://ktor.io/quickstart/guides/api.html)を参考に簡単に触って見ました

ちなみに読み方は「ケイター」らしいです
> kay-tor
[What is the proper way to pronounce ktor?](https://ktor.io/quickstart/faq.html#pronounce)

##プロジェクトの作成
[Setting up the project](https://ktor.io/quickstart/guides/api.html#setting-up-the-project)の「Open the pre-configured generator form」からプロジェクトを自動生成し、できたプロジェクトの`setting.gradle`をIDE(IntelliJ)で読み込みます
`build.gradle`は以下のような定義になっているはずです

``` groovy
buildscript {
    repositories {
        jcenter()
    }
    
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin'
apply plugin: 'application'

group 'api-example'
version '0.0.1-SNAPSHOT'
mainClassName = "io.ktor.server.netty.EngineMain"

sourceSets {
    main.kotlin.srcDirs = main.java.srcDirs = ['src']
    test.kotlin.srcDirs = test.java.srcDirs = ['test']
    main.resources.srcDirs = ['resources']
    test.resources.srcDirs = ['testresources']
}

repositories {
    mavenLocal()
    jcenter()
    maven { url 'https://kotlin.bintray.com/ktor' }
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    compile "io.ktor:ktor-server-netty:$ktor_version"
    compile "ch.qos.logback:logback-classic:$logback_version"
    compile "io.ktor:ktor-server-core:$ktor_version"
    compile "io.ktor:ktor-auth:$ktor_version"
    compile "io.ktor:ktor-auth-jwt:$ktor_version"
    compile "io.ktor:ktor-jackson:$ktor_version"
    testCompile "io.ktor:ktor-server-tests:$ktor_version"
}

```

##ビルドスクリプトの書き換え
生成されるビルドスクリプトが色々古いので書き換えていきます
### Gradleのバージョンアップ
自分がプロジェクトを生成した時はwrapperに`v4.8`が指定されていましたが最新は`v4.10.2`なのでアップデートします

``` sh
./gradlew wrapper --gradle-version=4.10.2 --distribution-type=bin
```

### compileをimplementationに書き換える
`compile`は非推奨なので`implementation`に書き換えます
参考：[Dependency configurations](https://docs.gradle.org/4.10.2/userguide/java_plugin.html#tab:configurations)

``` groovy
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    implementation "io.ktor:ktor-server-netty:$ktor_version"
    implementation "ch.qos.logback:logback-classic:$logback_version"
    implementation "io.ktor:ktor-server-core:$ktor_version"
    implementation "io.ktor:ktor-auth:$ktor_version"
    implementation "io.ktor:ktor-auth-jwt:$ktor_version"
    testImplementation "io.ktor:ktor-jackson:$ktor_version"
}
```

### GroovyをKotlinに書き換える
Gradle 5.0RCから正式版になる[Kotlin Gradle DSL](https://github.com/gradle/kotlin-dsl)に書き換えます(現在はRC版です)

`build.gradle`→`build.gradle.kts`
`setting.gradle`→`setting.gradle.kts`
にそれぞれ変更した後以下のようにコードを書き換えます
refs:
[Migrating build logic from Groovy to Kotlin](https://guides.gradle.org/migrating-build-logic-from-groovy-to-kotlin/)
[gradle/kotlin-dsl-samples](https://github.com/gradle/kotlin-dsl/tree/master/samples)
[SourceSets - Groovy -> Kotlin DSL #443](https://github.com/gradle/kotlin-dsl/issues/443)


``` kotlin:build.gradle.kts

import org.jetbrains.kotlin.cli.jvm.main
import org.jetbrains.kotlin.config.KotlinCompilerVersion

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        val kotlin_version: String by project
        classpath(kotlin("gradle-plugin", version = kotlin_version))
    }
}

plugins {
    application
    kotlin("jvm") version "1.3.10"
}

group = "triplan-api"
version = "1.0.0-SNAPSHOT"

application {
    mainClassName = "io.ktor.server.netty.EngineMain"
}

sourceSets {
    getByName("main").java.srcDirs("src")
    getByName("test").java.srcDirs("test")
    getByName("main").resources.srcDirs("resources")
    getByName("test").resources.srcDirs("testresources")

}

repositories {
    mavenLocal()
    jcenter()
    maven { url = uri("https://kotlin.bintray.com/ktor") }
}

dependencies {
    val ktor_version: String by project
    val logback_version: String by project

    implementation(kotlin("stdlib-jdk8", KotlinCompilerVersion.VERSION))
    implementation("io.ktor:ktor-server-netty:$ktor_version")
    implementation("ch.qos.logback:logback-classic:$logback_version")
    implementation("io.ktor:ktor-server-core:$ktor_version")
    implementation("io.ktor:ktor-auth:$ktor_version")
    implementation("io.ktor:ktor-auth-jwt:$ktor_version")
    implementation("io.ktor:ktor-jackson:$ktor_version")
    testImplementation("io.ktor:ktor-server-tests:$ktor_version")
}

```

##Jsonコンバーターの設定
デフォルトだとJacksonもしくはGsonを選択できますが[Kotlinと相性がよくない](https://qiita.com/egugue/items/f1f35c250f7a25768751)ので[Moshi](https://github.com/square/moshi)を利用したいと思います。
PRは出ているので、将来的にはサポートされるのかな..
[Moshi content negotiation support for servers #551](https://github.com/ktorio/ktor/pull/551)
上記のPRの[MoshiConverter.kt](https://github.com/ktorio/ktor/pull/551/files#diff-a34f608daee1f9d62baa26ecb99af852)だけ拝借します。Kotlinのバージョンが古く単純に取り込んでも動かないので、コルーチンをexperimental→正式版にあげます

```diff:MoshiConverter.kt
-import kotlinx.coroutines.experimental.io.ByteReadChannel
-import kotlinx.coroutines.experimental.io.jvm.javaio.toInputStream 
+import kotlinx.coroutines.io.ByteReadChannel
+import kotlinx.coroutines.io.jvm.javaio.toInputStream
```

`Moshi`はランタイム時にリフレクションで`JsonAdapter`を生成する方法と`Moshi Codegen`というコンパイル時にkaptで事前に`JsonAdapter`のコードを自動生成する方法の２つがありますが、今回は後者を利用します

```diff:build.gradle.kts/kaptの有効化
plugins {
kotlin("jvm") version "1.3.10"
+kotlin("kapt") version "1.3.10"
application
}
```

``` kotlin:依存ライブラリの追加
    implementation("com.squareup.moshi:moshi:$moshi_version")
    implementation("com.squareup.moshi:moshi-kotlin:$moshi_version")
    kapt("com.squareup.moshi:moshi-kotlin-codegen:$moshi_version")
```

```kotlin:レスポンスクラス
@JsonClass(generateAdapter = true)
data class Snippet(val text: String)
```
ここで一旦コマンドから`./gradlew build`を実行します。そうすると`build/generated/source/kaptKotlin/main/[packagename]`に`SnippetJsonAdapter`が生成されると思います。
※AndroidStudioだとIDE上からbuildすればkaptは自動実行されますが、IntelliJだとgradleメニューから`build`を実行しても生成されませんでした。。

```kotlin:Application.kt
install(ContentNegotiation) {
    moshi()
}
```
Ktorでは[Content Negotiation](https://ktor.io/servers/features/content-negotiation.html)でContent-TypeとAcceptに応じたカスタムの処理を定義することができます。
カスタムコンバーターを定義したい場合は`ContentConverter`を実装してリクエスト/レスポンス時のコンバート処理を定義することができます。
すでに組み込まれている`GsonConverter`/`JacksonConverter`、そして今回PRから拝借した`MoshiConverter`も上記インターフェースを実装しています

##リクエスト
[Routing](https://ktor.io/servers/features/routing.html)でリクエストの処理を定義します

```kotlin
val snippets = Collections.synchronizedList(
    mutableListOf(
        Snippet("hello"),
        Snippet("world")
    )
)
~~
    routing {
        get("/snippets") {
            call.respond(mapOf("snippets" to synchronized(snippets) { snippets.toList() }))
        }
    }
~~
```

## 結果
無事取得できました

```json
# GET localhost:8080/snippets

{
    "snippets": [
        {
            "text": "hello"
        },
        {
            "text": "world"
        }
    ]
}
```

## 最後に
Ktor自体というよりかはGradleやMoshiの話中心になってしましたが以上になります。
最後まで見ていただきありがとうございました。
今回のコードは全部下記にあります
https://github.com/aya2453/triplan-api