Kotlin 1.1.60 周辺ライブラリの導入

f:id:Naotsugu:20171116224116p:plain

November 13, 2017 に Kotlin 1.1.60 がリリースされました。


だからって訳ではないですが、以下について最初の一歩として簡単な導入部分を見ていきます。

  • kotlin-dsl によるGradle プロジェクトのビルド
  • Kotlin でサーブレット
  • Kotlin の SQLライブラリ Exposed
  • Kotlin の Web アプリケーションライブラリ Ktor


Kotlin スクリプトで Hello World

Gradle も 4.3 になり kotlin-dsl もいい感じになってきたので、Kotlin スクリプトで Hello World してみましょう。 ビルドファイルの拡張子を kt に変えるだけで、kotlin でビルドスクリプトが書けるという代物です。

新しめの Gradle なら同梱されているので特別な準備は必要ありません。


プロジェクトを作成します。gradle の init タスクを使います。

$ mkdir app
$ cd app
$ gradle init --type java-application


Gradle のビルドファイルを Kotlin スクリプトにリネームします。

$ mv build.gradle build.gradle.kts


build.gradle.kts を以下のように変更します。

plugins {
    application
    kotlin("jvm") version "1.1.60"
}
    
application {
    mainClassName = "samples.HelloWorldKt"
}
    
dependencies {
    compile(kotlin("stdlib"))
}
    
repositories {
    jcenter()
}


application プラグインで指定するクラスは末尾に Kt を付けたものになります。


パッケージを作成して HelloWorld.kt ファイルを作成します。

$ mkdir -p src/main/kotlin/samples
$ touch src/main/kotlin/samples/HelloWorld.kt


作成した HelloWorld.kt ファイルを以下のように編集します。

package samples
    
fun main(args: Array<String>) {
  println("Hello, world!")
}


実行します。

$ ./gradlew run


実行されました。

> Task :compileKotlin
Using Kotlin incremental compilation
    
> Task :run
Hello, world!


IntelliJ IDEA 2017.2 では build.gradle.kts を指定してプロジェクトのインポートできないので、今ならEarly Access Program の 2017.3 EAP を使った方が良いです。


Servlet

簡単なサーブレットを Tomcat で動かしてみましょう。


先程の build.gradle.kts を以下のように変更します。

plugins {
    application
    kotlin("jvm") version "1.1.60"
}
    
application {
    mainClassName = "samples.HelloWorldKt"
}
    
dependencies {
    compile(kotlin("stdlib"))
    compile("org.apache.tomcat.embed:tomcat-embed-core:8.5.23")
}
    
repositories {
    jcenter()
}


dependencies に tomcat-embed-core 加えただけです。



サーブレットクラスとTomcatの起動用処理を書きます。

package samples
    
import org.apache.catalina.core.StandardContext
import org.apache.catalina.startup.Tomcat
import java.io.File
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
    
    
fun main(args: Array<String>) {
    
    val tomcat = Tomcat()
    val docBase = File("src/main/webapp").absolutePath
    val ctx = tomcat.addContext("/app", docBase) as StandardContext
    
    Tomcat.addServlet(ctx, "hello", HelloController())
        .addMapping("/hello")
    
    tomcat.start()
    tomcat.server.await()
}
    
class HelloController : HttpServlet() {
    override fun doGet(req: HttpServletRequest, res: HttpServletResponse) {
        res.writer.write("Hello, World!!")
    }
}


実行してみましょう。

$ ./gradlew run
    
> Task :run
XX XX, 2017 XX:XX:XX org.apache.coyote.AbstractProtocol init
情報: Initializing ProtocolHandler ["http-nio-8080"]
XX XX, 2017 XX:XX:XX org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
情報: Using a shared selector for servlet write/read
XX XX, 2017 XX:XX:XX org.apache.catalina.core.StandardService startInternal
情報: Starting service [Tomcat]
XX XX, 2017 XX:XX:XX org.apache.catalina.core.StandardEngine startInternal
情報: Starting Servlet Engine: Apache Tomcat/8.5.23
XX XX, 2017 XX:XX:XX org.apache.coyote.AbstractProtocol start
情報: Starting ProtocolHandler ["http-nio-8080"]


ブラウザから http://localhost:8080/app/hello にアクセスすれば以下のように表示されます。

Hello, World!!


Exposed

JetBrains が作っているプロトタイプという位置づけのKotlin 向けの SQL ライブラリです。うまくすれば、ktorinx パッケージに標準ライブラリとして入るのかもしれません。


簡単に使い方をみてみましょう。


jcenter には登録されていないので、リポジトリに bintray を追加します。

plugins {
    application
    kotlin("jvm") version "1.1.60"
}
    
application {
    mainClassName = "samples.HelloWorldKt"
}
    
dependencies {
    compile(kotlin("stdlib"))
    compile("org.jetbrains.exposed:exposed:0.9.1")
    compile("com.h2database:h2:1.4.196")
    compile("ch.qos.logback:logback-classic:1.2.3")
}
    
repositories {
    jcenter()
    maven("https://dl.bintray.com/kotlin/exposed")
}


依存に exposed と合わせて H2 と logback を追加しました。



テーブルは org.jetbrains.exposed.sql.Table() を継承して定義します。

org.jetbrains.exposed.dao パッケージには Table() を継承した IdTable() があり、通常はそのサブクラスである IntIdTable() や LongIdTable() が利用できます。


以下のように object としてテーブル定義します。

object Users : IntIdTable() {
    val name = varchar("name", 50).index()
    val city = reference("city", Cities)
    val age = integer("age")
}
    
object Cities: IntIdTable() {
    val name = varchar("name", 50)
}



データベースの接続は Database.connect() で行います。

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.SchemaUtils.drop
    
fun main(args: Array<String>) {
    Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
    transaction {
        create (Cities, Users)
        //...
        drop (Users, Cities)
    }
}


SchemaUtils.create() でテーブル生成、SchemaUtils.drop() でテーブルのドロップを行うことができます。



Entity を以下のようにクラス定義することで、このクラス経由でデータアクセスができるようになります。

class User(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<User>(Users)

    var name by Users.name
    var city by City referencedOn Users.city
    var age by Users.age
}

class City(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<City>(Cities)

    var name by Cities.name
    val users by User referrersOn Users.city
}



以下のように 新しい Entity を作ることができ、データベースへ永続化されます。

val stPete = City.new {
  name = "St. Petersburg"
}

User.new {
  name = "b"
  city = stPete
  age = 27
}


Entity の一覧は all() で取得できます。

City.all().forEach {
  println(it.name)
}


検索は find で行います。

User.find { Users.age greaterEq 18 }.forEach {
  println(it.name)
}


更新はプロパティを書き換えるだけです。

stPete.name = "St.Petersburg"


idで取得して削除などは以下のようになります。

User[stPete.id].delete()


簡単ですね。



SQL の DSL も用意されているため、Entity 経由せず、直接 Table オブジェクトからSQLを実行することもできます。

val saintPetersburgId = Cities.insert {
  it[name] = "St. Petersburg"
} get Cities.id


Cities は object Cities: IntIdTable() として定義したテーブルオブジェクトで、テーブルに Insert しています。


Select は以下のようにできます。

for (city in Cities.selectAll()) {
    println("${city[Cities.id]}: ${city[Cities.name]}")
}


Join して取得することもできます。

(Users innerJoin Cities)
    .slice(Users.name, Users.cityId, Cities.name)
    .select {Cities.name.eq("St. Petersburg") or Users.cityId.isNull()}
    .forEach {
      
  if (it[Users.cityId] != null) {
    println("${it[Users.name]} lives in ${it[Cities.name]}")
  } else {
    println("${it[Users.name]} lives nowhere")
  }
}


groupBy による集約。

((Cities innerJoin Users)
    .slice(Cities.name, Users.id.count())
    .selectAll()
    .groupBy(Cities.name))
    .forEach {
    
  val cityName = it[Cities.name]
  val userCount = it[Users.id.count()]

  if (userCount > 0) {
    println("$userCount user(s) live(s) in $cityName")
  } else {
    println("Nobody lives in $cityName")
  }
}


update と delete。

Users.update({Users.id eq "alex"}) {
  it[name] = "Alexey"
}

Users.deleteWhere{Users.name like "%thing"}

Ktor

こちらも JetBrains が作っている Web アプリケーションフレームワークです。Kotlin 1.1 で(実験的)導入されたコルーチン使ったフレームワークになります。



build.gradle.kts を編集します。

例によって jcenter には登録されていないので、bintray のリポジトリを指定します。

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

application {
    mainClassName = "samples.HelloWorldKt"
}

dependencies {
    compile(kotlin("stdlib"))
    compile("ch.qos.logback:logback-classic:1.2.3")
    compile("io.ktor:ktor-server-core:0.9.0")
    compile("io.ktor:ktor-server-netty:0.9.0")

}

repositories {
    jcenter()
    maven ("http://dl.bintray.com/kotlin/ktor")
}


ktor-server-core と ktor-server-netty の依存を追加しました。

ktor では エンジン部分を差し替えできるようになっており、jetty や tomcat など色々ありますが、ここでは netty としています。



HelloWorld.kt を以下のように編集します。

package samples
    
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
    
fun main(args: Array<String>) {
  val server = embeddedServer(Netty, 8080) {
    routing {
      get("/") {
        call.respondText("Hello, world!", ContentType.Text.Html)
      }
    }
  }
  server.start(wait = true)
}


実行してみましょう。

$ ./gradlew run


コルーチンは実験的です、というワーニングが出ますが、

The feature "coroutines" is experimental (see: https://kotlinlang.org/docs/diagnostics/experimental-coroutines)


ブラウザから http://localhost:8080/ にアクセスすれば以下のように表示されます。

 Hello, world!


まだドキュメントは皆無に近いので、以下のサンプルを見ながら雰囲気をつかむことができます。

[https://github.com/ktorio/ktor/tree/master/ktor-samples]

まとめ

いくつかの Kotlin 周辺ライブラリやフレームワークについて見てみました。
最近 いろいろと野心的なプロジェクトが動いている Kotlin 界隈をのぞいてみるのも面白いですね。