RatpackはJavaおよびGroovy向けに作られていますが、JVMで動くということは当然、KotlinやScalaのようなほかの言語でも動作します。ここではKotlinでRatpackを書くとどんな感じなのか、ちょっとしたtipsを書いておきます。何かもっといいアイデアがありましたら、ぜひコメント欄でシェアしていただければ幸いです。
DSL風の記述
RatpackはGroovyのDSLを活用するため、多くの個所でラムダを引数に受け取るメソッドを持っています。例えば、サーバーにハンドラーを設定する部分は以下のように記述できます。
RatpackServer.start(spec -> {
spec.handlers(chain -> {
chain.get("index.html", ctx -> {
ctx.render(ctx.file("index.html"));
});
});
});
Kotlinはメソッドの引数の最後がラムダを受け取るとき、そのラムダを丸括弧の外側に記述することができます。また、ラムダの引数のプレースホルダーとして、it
を使用することができます。
RatpackServer.start {
it.handlers {
it.get("index.html") {
it.render(it.file("index.html"))
}
}
}
さすがにGroovyには劣りますが、DSL風の見た目でコードを記述することができます。
オペレーター関数
Ratpackは多くの部分でAction
という関数型インターフェースが用いられています。これはJava 8のConsumer
に相当するもので、何かの設定を行うのによく使われます。例えばハンドラーをサーバーに設定するhandlers()
メソッドは、Action<Chain>
を引数に持ちます。このチェーンにURLとハンドラーのマッピングを設定していくわけですが、ある程度ハンドラーが増えてくると、このAction
を分割したくなってきます。分割されたAction
はappend()
メソッドを使って一つにまとめることができます。でも何度もappend()
を繰り返すのは見た目が分かりづらくなります。出来れば中置記法を使いたいところです。
KotlinのOperator overloadingを使い、演算子で結合できるようにしましょう。
operator fun <T> Action<T>.plus(other: Action<in T>): Action<T> = this.append(other)
fun createHandlers() = AccountAction() + HistroyAction() + // ...
いうまでもなく、演算子オーバーロードの使い過ぎは厳禁です。
Pair
とDestructuring Declaration
Promise
のflatRight
などのメソッドは、複数のPromise
をまとめる、比較的よく使う便利なメソッドです。戻り値の型はRatpackにおけるタプルであるPair
型になります。
タプルは便利ではありますが、サポートのないプログラミング言語(つまりJava)ではleft
、right
のようなメソッドを使って値を取得しなければならず、アンチパターンとされる場合もあります。
Kotlinはタプルそのもののサポートはありませんが、代わりにDestructuring Declarationという機能で、疑似的なタプルを実現しています。これは各クラスにcomponentN()
(Nは数字)メソッドを持っていると、それぞれのcomponentN()
を分割して使用できる機能です。
RatpackのPair
には当然component1()
メソッドは持っていませんが、拡張メソッドとして実装することで、疑似的に分割代入できるようにします。
operator fun <T> Pair<T, *>.component1(): T = this.left
operator fun <T> Pair<*, T>.component2(): T = this.right
Pair
を分割しているこのような古いコードが
authenticator.checkLoggedIn(ctx).flatRight {
ctx.parse(ReportParameter::class.java)
}.nextOp { pair ->
val user = pair.left
val parameter = pair.right
// (...)
}
このように書けるようになります。
authenticator.checkLoggedIn(ctx).flatRight {
ctx.parse(ReportParameter::class.java)
}.nextOp { (user, parameter) ->
// (...)
}
惜しいのはIDEの補完が利かず、拡張メソッドのimport
文を自分で書かなければいけない点です。