こんばんは。フォートナイトやってたら台風の爆音がうるさくて敵の足音が聞こえず殺されたmorifujiです
2018/09/19 追記
gitlabに使ったコードを載せました。開発環境もdockerで構築できるようにしたので割と便利です。
概要
Kotlinを触りたいなあと思っていたのでその練習がてらにjoobyなるものを触りました。
- IntelliJでの環境構築
- RESTfulAPI実装/Swgger出力
- DBつなぎ込み(MySQL)
- ビルド
- docker化
までやったので知見を共有します。ちなみに僕はGradleとjavaはSIer時代3ヶ月ほどだけやっていましたがほぼ忘れている状態です
jooby
- 早くてスケーラブルなmicroフレームワーク
- ベンチマークそこそこ早い(https://www.techempower.com/benchmarks/
- その他、Railsと比較しているこちらの記事がすごく参考になりました
- https://www.mediamaxjapan.com/techblog/articles/why-we-choose-jooby/
- 噛み砕くと、Railsと比べて型安全・押し付けがましくない・パフォーマンス良いということでしょう
公式サイト:https://jooby.org/
IntelliJでの環境構築
こちらを参考にしました。あっという間です。https://github.com/jooby-project/kotlin-gradle-starter
git clone https://github.com/jooby-project/kotlin-gradle-starter.git
cd kotlin-gradle-starter
./gradlew joobyRun
IntelliJでrunする場合は以下の手順です、
- IntelliJでプロジェクト開いて、右上のGradleを選択
-
jooby>joobyRun
を起動
...
[2018-09-04 23:15:13,805]-[Hotswap] INFO starter.kotlin.App - [dev@netty]: Server started in 3873ms
GET / [*/*] [*/*] (/anonymous)
listening on:
http://localhost:8080/
まだbuild.gradleにもなにも触っていない状態ですが、http://localhost:8080/
を叩くとHelloWorldされているのが確認できます
ホットリロード
実はこのjoobyRun
コマンドは裏でホットリロードも動いています。対象ファイルは.classと.confと.propertiesです。静的ファイルはさすがにリロードしてくれないみたいですね
こんな感じでカスタマイズも可能みたいです
joobyRun {
mainClassName = 'com.mycompany.App'
compiler = 'on'
includes = ['**/*.class', '**/*.conf', '**/*.properties']
excludes = []
logLevel = 'info'
srcExtensions = [".java", ".kt", ".conf", ".properties"]
}
LL系と比べると流石に遅いですが、自動でやってくれること自体に感動しますね、
RESTfulAPI実装
jacksonというライブラリが公式で紹介されてます。
build.gradleにライブラリを追加しましょう
dependencies {
compile "org.jooby:jooby-lang-kotlin"
compile "org.jooby:jooby-netty"
compile "io.netty:netty-transport-native-epoll:${dependencyManagement.importedProperties['netty.version']}:${osdetector.classifier.contains('linux') ? 'linux-x86_64' : ''}"
compile "io.netty:netty-tcnative-boringssl-static:${dependencyManagement.importedProperties['boringssl.version']}:${osdetector.classifier}"
+ compile "org.jooby:jooby-jackson:$joobyVersion"
testCompile "org.jetbrains.spek:spek-api:$spekVersion"
testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$spekVersion"
testCompile "org.junit.platform:junit-platform-launcher:$junitPlatformVersion"
testCompile "org.amshove.kluent:kluent:1.35"
testCompile "io.rest-assured:rest-assured:3.1.0"
}
さらに、メインクラスを修正しましょう
+ data class People(val name: String, var age: Int)
/**
* Gradle Kotlin stater project.
*/
class App : Kooby({
+ use(Jackson())
get {
val name = param("name").value("Kotlin")
"Hello $name!"
}
+ get("/array") { req ->
+ val arr = listOf(1,2,3,4,5)
+ arr
+ }
+
+ get("/map") { req ->
+ val map = mapOf("hoge" to "fuga", "りんご" to "ごりら")
+ map
+ }
+
+ get("/data_class") {req->
+ val people = People("金田哲夫", 19)
+ people
+ }
この時、IntelliJ上でパッケージの読み込みがうまくいかず、警告が表示されるはずです。後半にハマりポイントとして解消方法を書いたので参考にして下さい
この修正でホットリロードが動いた後にブラウザで叩くとわかりますがresponseのContent-Typeがjsonになっておりなおかつ、
/list
のresponseはarrayのjsonとして表示され、/map
のresponseはobjectとして表示され、/data_class
のresponseはobjectとして表示されます。
Swaggerを生成
今度はこのRESTfulAPIのAPI仕様書を自動生成しましょう。
swaggerとramlの自動生成が紹介されています。
dependencies {
compile "org.jooby:jooby-lang-kotlin"
compile "org.jooby:jooby-netty"
compile "io.netty:netty-transport-native-epoll:${dependencyManagement.importedProperties['netty.version']}:${osdetector.classifier.contains('linux') ? 'linux-x86_64' : ''}"
compile "io.netty:netty-tcnative-boringssl-static:${dependencyManagement.importedProperties['boringssl.version']}:${osdetector.classifier}"
compile "org.jooby:jooby-jackson:$joobyVersion"
+ compile "org.jooby:jooby-apitool:$joobyVersion"
testCompile "org.jetbrains.spek:spek-api:$spekVersion"
testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$spekVersion"
testCompile "org.junit.platform:junit-platform-launcher:$junitPlatformVersion"
testCompile "org.amshove.kluent:kluent:1.35"
testCompile "io.rest-assured:rest-assured:3.1.0"
}
/**
* Gradle Kotlin stater project.
*/
class App : Kooby({
use(Jackson())
+ use(ApiTool().swagger().raml())
get {
val name = param("name").value("Kotlin")
"Hello $name!"
}
...
これでホットリロードを回した後、複数のエンドポイントが自動で追加されています。
GET /swagger/swagger.json [*/*] [*/*] (/anonymous)
GET /swagger/swagger.yml [*/*] [*/*] (/anonymous)
GET /swagger/static/** [*/*] [*/*] (/anonymous)
GET /swagger/static/** [*/*] [*/*] (/anonymous)
GET /swagger [*/*] [*/*] (/anonymous)
GET /raml/api.raml [*/*] [*/*] (/anonymous)
GET /raml/static/** [*/*] [*/*] (/anonymous)
GET /raml
/swagger
を叩くとこんな感じで自動生成されてます、あとは煮るやり焼くなりできますね
MySQLつなぎ込み
MySQLのつなぎ込みがしたかったのでやりました、SQLは書きたくないので、Hibernateを採用しました
conf/application.conf
を開き、追記。
# mysql
# add or override properties
# See https://github.com/typesafehub/config/blob/master/HOCON.md for more details
+ # mysql
+ db {
+ url: "jdbc:mysql://localhost:3111/test",
+ user: "root",
+ password: "password"
+ }
dependenciesに追加。僕はmysqlですが、ドライバを変えればポスグレでもなんでもいけると思います
dependencies {
compile "org.jooby:jooby-lang-kotlin"
compile "org.jooby:jooby-netty"
compile "io.netty:netty-transport-native-epoll:${dependencyManagement.importedProperties['netty.version']}:${osdetector.classifier.contains('linux') ? 'linux-x86_64' : ''}"
compile "io.netty:netty-tcnative-boringssl-static:${dependencyManagement.importedProperties['boringssl.version']}:${osdetector.classifier}"
compile "org.jooby:jooby-jackson:$joobyVersion"
compile "org.jooby:jooby-apitool:$joobyVersion"
+ compile "org.jooby:jooby-jdbc:$joobyVersion"
+ compile "org.jooby:jooby-hbm:$joobyVersion"
+ compile "mysql:mysql-connector-java:5.1.47"
testCompile "org.jetbrains.spek:spek-api:$spekVersion"
testRuntime "org.jetbrains.spek:spek-junit-platform-engine:$spekVersion"
testCompile "org.junit.platform:junit-platform-launcher:$junitPlatformVersion"
testCompile "org.amshove.kluent:kluent:1.35"
testCompile "io.rest-assured:rest-assured:3.1.0"
}
まずは、entity作成(kotlin-gradle-starter/src/main/kotlin/starter/kotlin/entity/Contact.kt)
package starter.kotlin.entity
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
@Entity(name = "contacts")
class Contact {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private var id: Int? = null
private val name: String? = null
var notes: String? = null
protected var website: String? = null
private var starred: Int = 0
private var password: String? = null
fun setPassword(rawPassword: String) {
this.password = rawPassword
}
fun review(isUp: Boolean) {
if (isUp) {
this.starred++
} else {
this.starred--
}
}
}
controllerもとりあえず作成。em
というのが、EntityManagerで、処理のファサードになっている
/**
* Gradle Kotlin stater project.
*/
class App : Kooby({
use(Jackson())
use(ApiTool().swagger().raml())
+ use(Jdbc())
+ use(Hbm().classes(Contact::class.java))
+ get("/api/contact/") { req ->
+ require(UnitOfWork::class.java).apply { em ->
+ // 新規作成
+ val c = Contact()
+ // publicなのでOK
+ c.notes = "メモだよ!!!そのままinsert!!"
+ // privateなので
+ c.review(true)
+ c.review(true)
+ // privateなので
+ c.setPassword("ほげほげ")
+
+ // 登録
+ em.save(c)
+
+ // さらに編集
+ c.review(false)
+ c.notes = "修正済み(´・ω・`)"
+ // さらに保存
+ em.save(c)
+
+ // 一覧取得
+ em.createQuery("from contacts").resultList
+ }
+ }
get {
val name = param("name").value("Kotlin")
"Hello $name!"
}
...
DBに繋がる状態でjoobyRun
してください。起動時にmysqlへの疎通確認が走ります。と同時にcontacts
テーブルが自動で生成されています!!コンパイラ型言語っぽいですよね〜
この状態で叩くと、こんな感じのresponseになると思います。(5回叩きました)
各entityのプロパティがnotes
しかないのは、Contactクラス
のpublicなプロパティだからです。
テーブルを自動作成したり、entityクラスのアクセス修飾子によってresponse変えたりするところを見ると、php等のORMよりもさらにDBとアプリケーションが密結合になっている感じがします。
ビルド
jarファイルを出力して、jar単体で動くかテストします。
Gradle
(画面右上) > build
> build
からビルド
成功したら、build/libs
にjarファイルが出力されてます
~/jooby
❯ java -jar ./kotlin-gradle-starter/build/libs/kotlin-gradle-starter-1.0.jar
./kotlin-gradle-starter/build/libs/kotlin-gradle-starter-1.0.jarにメイン・マニフェスト属性がありません
manifestが入っていないらしいので、追加。
...
jar {
manifest {
attributes(
'Main-Class': "starter.kotlin.AppKt"
)
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
特にfrom { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
この部分を忘れないよう注意してください
再ビルドしてもういちどデプロイ
❯ java -jar ./kotlin-gradle-starter/build/libs/kotlin-gradle-starter-1.0.jar
[2018-09-04 22:19:30,882]-[main] INFO com.zaxxer.hikari.HikariDataSource -
...
GET /swagger/swagger.json [*/*] [*/*] (/anonymous)
GET /swagger/swagger.yml [*/*] [*/*] (/anonymous)
GET /swagger/static/** [*/*] [*/*] (/anonymous)
GET /swagger/static/** [*/*] [*/*] (/anonymous)
GET /swagger [*/*] [*/*] (/anonymous)
GET /raml/api.raml [*/*] [*/*] (/anonymous)
GET /raml/static/** [*/*] [*/*] (/anonymous)
GET /raml [*/*] [*/*] (/anonymous)
GET / [*/*] [*/*] (/anonymous)
GET /array [*/*] [*/*] (/anonymous)
GET /map [*/*] [*/*] (/anonymous)
GET /data_class/:name [*/*] [*/*] (/anonymous)
listening on:
http://localhost:8080/
キタ━━━━━━━━m9( ゚∀゚)━━━━━━━━!!
joobyの特徴の一つに、サーブレットの概念がなく、jarファイルにサーバーも含まれているため、簡単にデプロイできると書かれています。サーバーはjettty/nettyほか多数から選択できるみたいです。
こういう丸ごと入ったjarファイルをfatJarって呼ぶらしいですね。fatって悪いイメージだけどいいのか笑
docker化
これが一番しんどかった、、
公式には「こんなかから適当に選んでやってみー多分できるやろ(ハナホジ」みたいな感じでgradleのプラグイン検索ページのリンクが貼っていました、どうしたらええんや、、
とりあえずgradle+docker
でメジャーそうな com.palantir.docker-runを使うことにしました
これを使用できるようbuild.gradle修正
buildscript {
...
repositories {
mavenLocal()
jcenter()
mavenCentral()
+ maven {
+ url "https://plugins.gradle.org/m2/"
+ }
}
dependencies {
classpath "com.google.gradle:osdetector-gradle-plugin:1.4.0"
classpath "io.spring.gradle:dependency-management-plugin:1.0.4.RELEASE"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jooby:jooby-gradle-plugin:$joobyVersion"
classpath "org.junit.platform:junit-platform-gradle-plugin:$junitPlatformVersion"
+ classpath "gradle.plugin.com.palantir.gradle.docker:gradle-docker:0.13.0"
}
}
...
apply plugin: "jooby"
apply plugin: "org.junit.platform.gradle.plugin"
+apply plugin: 'com.palantir.docker'
...
さらにこちらも参考にして、dockerプラグインの設定を記載。
docker {
name "${project.group}/morimorikochan" // 任意の名前で
files "{フルパス}/kotlin-gradle-starter/build/libs"
buildArgs(['JAR_FILE': 'kotlin-gradle-starter-1.0.jar'])
}
また、docker内からアクセスできるDBをconf/application.conf
に記載した上で以下のコマンドを叩きましょう。(docker内からDBにアクセスできないとエラーで落ちるので)
./gradlew docker
途中でエラーになるかたは後半にハマりポイントに解消方法を書いてるので参考にしてください、ぼくはこれで1時間とかしました
うまくいくと、プラグインのname
プロパティで指定したイメージ名でdockerのイメージが作成されています。
~/jooby/kotlin-gradle-starter master* 8s
❯ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
starter.kotlin/morimorikochan latest 688cabd7a3dc 8 minutes ago 1.01GB
<none> <none> eee987ddfeb1 3 hours ago 1.01GB
<none> <none> cc016de61c54 3 hours ago 1.01GB
...
あとはこれをrunさせれば
docker run -it --rm starter.kotlin/morimorikochan
キタ━━━━━━━━m9( ゚∀゚)━━━━━━━━!!
ハマったこと
IntelliJ上でbuild.gradleに追加した新しいパッケージで警告が出る
この原因は、IntelliJ上でパッケージが認識されていないのが原因みたいです、
File > Invalidate Caches/Restart
でも治りませんでしたが、Gradle
(画面右上) > build setup
> wrapper
を実行すると読み込まれました。もっと簡単な方法がありそう
dockerが実行できない(Cannot run program "docker": error=2, No such file or directory)
./gradlew docker
をしても、途中でCannot run program "docker": error=2, No such file or directory
となるときがあります。
その時はターミナル上で
./gradlew --stop
をしましょう。gradleのデーモンが停止します。その上でもう一度./gradlew docker
をするとビルドできるはずです
https://github.com/Transmode/gradle-docker/issues/80#issuecomment-348476060
所管
- Kotlin書き方が面白い
- クセがあるので慣れるまで時間かかりそう
- joobyは思ったより今風な感じがした
- プラグインとして機能が提供されているので、カスタマイズが容易にできそう
- xmlで設定しなくていいことに感動した
- コードとか設定周りがわりとDRY
- アノテーションも最小限でコードを追えばすぐわかるフレームワークだと思った
- nettyの起動早すぎ
- docker-composeでmysqlと連携させたら、joobyの疎通確認早すぎてmysqlが起動中でjoobyが死ぬ
- 最近のFWなのでドキュメントが貧弱かと思ったけどそんなこともなかった。モジュールを使えば大体のユースケースを満たせそう!!!
- 実務で使ってみたい!!!
- 誰かjoinさせてください
- Hibernateもちょっとクセが強そう
- その分細かいとこまでさわれそう