この記事はKotlin Advent Calendar 2016の12日目の記事です。
Kotlin Advent Calendar 2016 - Qiita
さて、今年は一部のmicroservicesをKotlinで実装してリリースしたりしたので個人的にはKotlinを実戦投入した記念すべき年とも言えます? このmicroservicesはSpringBootで実装されていますが、最近はSpark Frameworkのようなmicroフレームワークに注目してます。
Spark Framework
Spark Frameworkとはサーバを同梱した(中身はJetty)軽量なWebフレームワークです。ちなみに、SparkといえばApache Sparkが有名ですが完全に別物なのであしからず。
Spark Framework - A tiny Java web framework
Spark Frameworkの特徴としては標準でJava8のLambdaに対応しているのでコールバックを書きやすいというメリットがありますね。
Spark Framework with Kotlin
とはいえJava8 Lamdbaでは実装上満足できないのでKotlinで書いてみたくなった。つまり、Spark Framework with Kotlin。Lambdaだけでは言うほど生産性は上がらないし、ちゃんとKotlinの機能をふんだんに使って生産性高く開発したいところ。やっぱりdata classは使いたいですし。
Java8で書く
SparkをオーソドックスにJava8で書くと以下のようなコードになる。
import static spark.Spark.*;
public class Server {
public static void main(String[] args) {
get("/echo", (req, res) -> "Hello, " + req.queryParams("name") + "!");
}
}
ルーティングを定義して、リクエストを処理するハンドラをLambda式で渡すだけ。1年ぐらいGolangをやっていたので、この書き方には多少親近感ありますね。
Kotlinで書く
これをKotlin化すると以下のような感じになります。このくらいではそんなに違いはないですね。文字列を埋め込みできるくらい。
package io.stormcat.sandbox
object Server {
@JvmStatic
fun main(args: Array<String>) {
get("/echo", { req, res ->
"Hello, ${req.queryParams("name")}!"
})
}
}
Controllerを分ける
ルーティング増えるとmainが太るのは目に見えてるので、リクエストのハンドラはちゃんとControllerとして別で定義します。
package io.stormcat.sandbox.controller
import spark.Route
class EchoController {
val echo = Route { req, res ->
"Hello, ${req.queryParams("name")}!"
}
}
ルーティング側ではcontrollerを参照するようにします。
package io.stormcat.sandbox
object Server {
@JvmStatic
fun main(args: Array<String>) {
val echo = EchoController()
get("/echo", echo.echo)
}
}
Service層とDIを導入してみる
実際開発していくと、ちゃんとレイヤーを分けたいという欲求が出てきそうです。というわけで適当にechoするロジックをService層に定義します。SparkはシンプルにWebサーバとしての機能に専念してるので、Guiceでも使ってDIできるようにしてみる。
これがService。
package io.stormcat.sandbox.service
class EchoService {
fun echo(value: String): String {
return "Hello, $value!"
}
}
ControllerはServiceに依存するようにする。依存性は @Inject
で定義。なんか良さそうです。
package io.stormcat.sandbox.controller
import io.stormcat.sandbox.service.EchoService
import spark.Route
import javax.inject.Inject
class EchoController @Inject constructor(
val echoService: EchoService
) {
val echo = Route { req, res ->
echoService.echo(req.queryParams("name"))
}
}
依存性の注入はGuiceに頼ります。とりあえずスコープとか何も考えないでただぶっこんでます。
package io.stormcat.sandbox
import com.google.inject.Guice
import io.stormcat.sandbox.controller.EchoController
import spark.Spark.get
object Server {
@JvmStatic
fun main(args: Array<String>) {
val injector = Guice.createInjector()
val echo = injector.getInstance(EchoController::class.java)
get("/echo", echo.echo)
}
}
JSONを返してみる
Controllerを修正してJSONを返すようにしてみます。Controllerの戻り値はdata classを返すようにしてます。
package io.stormcat.sandbox.controller
import io.stormcat.sandbox.service.EchoService
import spark.Route
import javax.inject.Inject
class EchoController @Inject constructor(
val echoService: EchoService
) {
data class EchoMessage(
val body: String
)
val echo = Route { req, res ->
EchoMessage(
body = echoService.echo(req.queryParams("name"))
)
}
}
JSONレスポンスを返すために、ResponseTransformerを実装します。JSONのシリアライザーは何でもよくて、サンプルにあったgsonをそのまま使ってみた。
package io.stormcat.sandbox
import com.google.gson.Gson
import spark.ResponseTransformer
class JsonTransformer : ResponseTransformer {
val gson = Gson()
override fun render(model: Any?): String {
return gson.toJson(model)
}
}
ルーティング側ではこうなりますね。
package io.stormcat.sandbox
import com.google.inject.Guice
import io.stormcat.sandbox.controller.EchoController
import spark.Spark.get
object Server {
@JvmStatic
fun main(args: Array<String>) {
val injector = Guice.createInjector()
val echo = injector.getInstance(EchoController::class.java)
get("/echo", echo.echo, JsonTransformer())
}
}
実際にリクエスト投げてみましょう。
$ curl http://localhost:4567/echo?name=nekotan
{"body":"Hello, nekotan!"}%
こんな感じでJSON返ってきます。
build.gradle
最後にbuild.gradle晒しておきます。基本的にKotlinがビルドできるようになってればOK。Sparkはほんと依存が少なくていい感じ。
group 'io.stormcat.sandbox'
version '0.0.1-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.0.4'
ext.spark_version = '2.5.4'
ext.swagger_version = '1.5.9'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: 'idea'
mainClassName='io.stormcat.sandbox.Server'
processResources.destinationDir = compileJava.destinationDir
compileJava.dependsOn processResources
kapt {
generateStubs = false
}
idea {
module {
inheritOutputDirs = false
outputDir = file("$buildDir/classes/main/")
}
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "com.sparkjava:spark-core:$spark_version"
compile "com.google.inject:guice:4.1.0"
compile "com.google.code.gson:gson:2.8.0"
}
所感
SpringとかFrameworkの使い方を覚えるのにそこそこコストを払うと思うけど、Spark Frameworkはかなり直感的に書けるのが個人的にはいいかなーと思ってます。あとはApache Sparkとかぶるのでググラビリティに多少難点があるといったとこですかねw
というわけでSparkしながら来年もコトリンことりんしていこうと思いを馳せる今日この頃であります。