mike-neckのブログ

Java or Groovy or Swift or Golang

Hello r2dbc with Kotlin

リアクティブな感じで RDB に接続できるやつ。内容的にはバファさんのブログの下位互換未満。

bufferings.hatenablog.com


準備1

使ったデータベースは postgres で、docker で用意した。とりあえず、こんな感じのテーブルを作っておく。

create table users(
  id bigint not null primary key ,
  name varchar(31) not null unique ,
  created timestamp not null 
);

ついでにデータを入れておく

insert into users (id, name, created)
values (1, 'test-user', '2019-01-01 10:00:00'),
       (2, 'test-admin', '2019-01-15 10:00:00'),
       (3, 'test-owner', '2019-02-01 10:00:00'),
       (4, 'test-guest', '2019-02-15 10:00:00');

準備2

gradle は次のような感じ(Kotlin)

import java.net.URI

plugins {
    id("org.jetbrains.kotlin.jvm").version("1.3.20")
}

repositories {
    jcenter()
    mavenCentral()
    maven {
        url = URI.create("https://repo.spring.io/libs-milestone/")
    }
}
dependencies {
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.1.1")

    implementation("io.projectreactor:reactor-core:3.2.6.RELEASE")
    implementation(group = "io.r2dbc", name = "r2dbc-postgresql", version = "1.0.0.M7")

    testImplementation("org.junit.jupiter:junit-jupiter:5.4.0")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.3.21")
    testImplementation("io.projectreactor:reactor-test:3.2.6.RELEASE")
}

ちょっと動かす

テストデータは入っているので、クエリーを投げるところです

@Test
fun noKoroutine() {
  val options = ConnectionFactoryOptions.builder()
      .option(ConnectionFactoryOptions.DRIVER, "postgresql")
      .option(ConnectionFactoryOptions.HOST, "localhost")
      .option(ConnectionFactoryOptions.PORT, 5432)
      .option(ConnectionFactoryOptions.USER, "postgres-user")
      .option(ConnectionFactoryOptions.PASSWORD, "postgres-pass")
      .option(ConnectionFactoryOptions.DATABASE, "postgres")
      .build()
  val connectionFactory: ConnectionFactory = ConnectionFactories.get(options)
  val conn = Mono.from(connectionFactory.create())
  val users = conn.flatMapMany { connection ->
    connection.createStatement(
        //language=SQL
        "SELECT u.id, u.name, u.created FROM users AS u WHERE u.name = $1")
        .bind("$1", "test-user")
        .execute() }
      .flatMap { result ->
        result.map { row, meta ->
          val name = row.get("name", String::class.javaObjectType)
          val id = row.get("id", Long::class.javaObjectType)
          val created = row.get("created", Instant::class.java)
          return@map if (created != null && name != null && id != null) User(id, name, created)
          else null
        }
      }
      .filter { it != null }
      .cast(User::class.java)
  runBlocking {
    users.buffer().consumeEach {
      println(it)
    }
  }
}

実行結果

f:id:mike_neck:20190317201815p:plain


番外編

Kotlin-Coroutine を使うといい感じのコードになるのかと思ったけど、

// GlobalScope.flux 内
val result = connection.createStatement(
        //language=SQL
        "SELECT u.id, u.name, u.created FROM users AS u WHERE u.name = $1")
        .bind("$1", "test-user")
        .execute()
        .awaitSingle()

result.map { row, _ ->
    val name = row.get("name", String::class.javaObjectType)
    val id = row.get("id", Long::class.javaObjectType)
    val created = row.get("created", Instant::class.java)
    return@map if (created != null && name != null && id != null) User(id, name, created)
    else null
}.consumeEach { if (it != null) channel.offer(it) }

f:id:mike_neck:20190317212450p:plain

AbstractByteBuff#getCharaSequence のあたりで、 ByteBuffreadableBytes0 になってしまう現象に遭遇したので、諦めました

Kotlin Coroutine はもう少し業務寄りの部分で多様なデータが踊っているような箇所で使って、こういう複雑にデータをさわらない箇所ではあまり効果がなさそうに思いました(要検討)。

Kotlin で groovy.lang.Closure のインスタンスを生成・取得する(G* Advent Calendar 2017 6日目)

G* Advent Calendar 2017 の 6日目は groovy.lang.Closureインスタンスを Kotlin で生成・取得する方法です。

f:id:mike_neck:20150917235151p:plain
Groovy!

3分くらいで読み終われます。


groovy.lang.Closureインスタンスを Kotlin で生成・取得する方法です。Kotlin コードの中で Closure を作って Groovy に渡すということを指しています。


生成方法

早速ですが生成方法です。

ある A というクラスがあって、 Closure の中で用いるプロパティを A から取得したい場合は次のような関数を書きます。

fun <A> closure(a: A, cl: A.() -> Unit): Closure<Unit> = object: Closure<Unit>(a) {
  fun doCall() = this@closure.cl()
}

ちょっと欲張りに A というクラスで宣言した Closure で、中で用いるプロパティを B から取得しつつ、 C を引数で受け取って、 D を返す Closure を生成したい場合は次のような関数を書きます。

fun <A, B, C, D> closure(declaredAt: A, receiver: B, cl: B.(C) -> D): Closure<D> = object: Closure<Unit>(this) {
  init {
    delegate = receiver
  }
  fun doCall(c: C): D = this@closure.cl(c)
}

試してみる

最後の欲張りな記述方法を用いた例を書いてみます。次のような Kotlin コードを書きます。

object ClosureCreation {
  @JvmStatic
  fun <A> stringToIntClosure(declaredAt: A, delegate: Supplier<String>): Closure<Int> =
      closure<A, Supplier<String>, String, Int>(declaredAt, delegate) { s -> "${get()} $s".length }
}

この stringToIntClosure 関数を呼ぶと java.util.function.Supplier を引数にとって、kotlin.Int(実質は int) が返ってくる groovy.lang.Closure が返ってきます。

そして、こちらが利用するコードになります。

class ClosureCreationTest {
  @Test
  void 'Kotlin で 作った closure を Groovy で扱う'() {
    def closure = ClosureCreation.stringToIntClosure(this, [get: { 'Groovy' }] as Supplier<String>)
    assert closure('Calling closure made by Kotlin.') == 'Groovy Calling closure made by Kotlin.'.size()
  }
}

こちらは試していただければテストが通ることがわかるかと思います。


仕組み

Groovy の Closurejava.util.concurrent.Callable インターフェース と Runnable インターフェースを実装した抽象クラスになっています。

f:id:mike_neck:20171202144126p:plain

そして、それぞれの実装は次のようになっています。

public void run() {
  call();
}
public V call() {
  final Object[] NOARGS = EMPTY_OBJECT_ARRAY;
  return call(NOARGS);
}
public V call(Object... args) {
  return (V) getMetaClass().invokeMethod(this, "doCall", args);
}

invokeMethod という名前から想像がつく通り、最終的には doCall メソッドを動的に取得して実行します。したがって、欲しいパラメーターの型と戻り値の型をつかった doCall メソッドを object 式で作れば、 Groovy でなくても Closureインスタンスを生成できるという事になります。

また、先程までの説明ではプロパティは〜〜から取得するという説明をしましたが、これを実現するのが Closuredelegate です。何かしらのメソッド、フィールド(プロパティ)を Closure 内で取得する時の取得元になります。これは Kotlin のレシーバーつき関数(Foo.(Bar) -> Baz)のようなものです。Gradleのドキュメントなどで Closure を受け取るメソッドについては、たいてい 〜〜 is passed as the closure's delegate のように書かれていたりします。

応用

先日ブログに書いた、 junit-starter-plugin においても、ここで書いた変換を行っています。

mike-neck.hatenadiary.com

gradle-plugin-publish-plugin の DSL にて PluginBundleExtension を介して gradle plugin の id(plugins ブロックで指定するやつ)と displayName を設定しますが、PluginBundleExtensions 内部の plugins ブロックの引数が groovy.lang.Closure であるため、Kotlin の関数・レシーバーつき関数ではここを記述できないため、ここに用いた変換用の関数を使っています。

おわり

Gradle kotlin DSL の便利機能

f:id:mike_neck:20171113232305p:plain

Gradle のスクリプトを Kotlin で書いていると、タスクが自動的に二つ足されていることに気づいたので、それを使ってみたら、わりと便利だった。


kotlinDslAccessorsReport タスク

現在のプロジェクトが利用できる extension と convention のKotlin 実装コードを表示するタスク.

表示内容からは、現在のプロジェクトにて Kotlin DSL の中で呼び出すことのできるAPIの関数名やパラメーターの型がわかる.

例えば先日リリースしたこちらのプラグインを適用した状態で、 kotlinDslAccessorsReport タスクを実行する.

mike-neck.hatenadiary.com

(スクリプト本体)

plugins {
    id("org.mikeneck.junit.starter.library") version("5.0.2")
}
repositories {
    mavenCentral()
}

(出力内容(一部抜粋))

/**
 * Configures the [junitPlatform][org.junit.platform.gradle.plugin.JUnitPlatformExtension] project extension.
 */
fun Project.`junitPlatform`(configure: org.junit.platform.gradle.plugin.JUnitPlatformExtension.() -> Unit): Unit =
    extensions.configure("junitPlatform", configure)

/**
 * Retrieves the [junit][org.mikeneck.junit.starter.JunitExtra] project extension.
 */
val Project.`junit`: org.mikeneck.junit.starter.JunitExtra get() =
    extensions.getByName("junit") as org.mikeneck.junit.starter.JunitExtra

/**
 * Configures the [junit][org.mikeneck.junit.starter.JunitExtra] project extension.
 */
fun Project.`junit`(configure: org.mikeneck.junit.starter.JunitExtra.() -> Unit): Unit =
    extensions.configure("junit", configure)

/**
 * Adds a dependency to the 'api' configuration.
 *
 * @param dependencyNotation notation for the dependency to be added.
 * @param dependencyConfiguration expression to use to configure the dependency.
 * @return The dependency.
 *
 * @see [DependencyHandler.add]
 */
inline
fun DependencyHandler.`api`(
    dependencyNotation: String,
    dependencyConfiguration: ExternalModuleDependency.() -> Unit): ExternalModuleDependency =
    add("api", dependencyNotation, dependencyConfiguration)

出力内容例の一番最後の部分から、この java-library プロジェクトで 依存ライブラリーを追加する際には DependencyHandler のブロック内で api という関数に 文字列で依存ライブラリーの文字列表現とその設定用の レシーバー付き関数を渡せばよいということがわかる.

他にも、先日のプラグインの記事には書かなかった(つまり隠れコマンド的な) junit という extension の存在も見破られている.(なお、この extension は configure できないのだが…)


kotlinDslAccessorsSnapshot タスク

こちらも、 kotlinDslAccessorsReport とほぼ同様な感じで、現在のプロジェクトで利用可能な conventions / extensions / configurations の一覧を json 形式(ファイル名は project-schema.json)で gradle ディレクトリーの下に出力する.

(出力内容例)

{
    ":": {
        "conventions": {
            "base": "org.gradle.api.plugins.BasePluginConvention",
            "java": "org.gradle.api.plugins.JavaPluginConvention"
        },
        "configurations": [
            "api",
            "apiElements",
            "testRuntimeOnly"
        ],
        "extensions": {
            "ext": "org.gradle.api.plugins.ExtraPropertiesExtension",
            "defaultArtifacts": "org.gradle.api.internal.plugins.DefaultArtifactPublicationSet",
            "junit": "org.mikeneck.junit.starter.JunitExtra",
            "gradlePlugin": "org.gradle.plugin.devel.GradlePluginDevelopmentExtension"
        }
    }
}

ただ、こちらの出力は何に使うのかイマイチわからなかった…