Java
Kotlin
ratpack

Ratpack入門 (番外編) - Kotlinで書くRatpack

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を分割したくなってきます。分割されたActionappend()メソッドを使って一つにまとめることができます。でも何度も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

PromiseflatRightなどのメソッドは、複数のPromiseをまとめる、比較的よく使う便利なメソッドです。戻り値の型はRatpackにおけるタプルであるPair型になります。
タプルは便利ではありますが、サポートのないプログラミング言語(つまりJava)ではleftrightのようなメソッドを使って値を取得しなければならず、アンチパターンとされる場合もあります。

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文を自分で書かなければいけない点です。