LoginSignup
9
6

More than 3 years have passed since last update.

Spring WebfluxとKotlinCoroutine

Last updated at Posted at 2019-12-17

こちら、Kotlinアドベントカレンダー 18日目の記事です。
こんばんはこんにちは、今年は様々な事がありました。東京で暮らし始めたSatohJohnです。
今回は社内開発で利用したKotlinでのSpring WebFlux(主にrouter)の書き方についてです。
Spring WebFlux自体については、ググれば色々調べれば出てくると思います!!素晴らしい!!

環境

Gradle(pluginId or artifactId) バージョン
Spring Boot org.springframework.boot 2.2.2.RELEASE
kotlin org.jetbrains.kotlin.jvm
org.jetbrains.kotlin.plugin.spring
1.3.61
kotlinx kotlinx-coroutines-core
kotlinx-coroutines-reactor
1.3.3

書き方

JavaでのSpring WebfluxのRouter

routerに対して Mono<ServerResponse> を返却する形になります。

@Bean
public RouterFunction<ServerResponse> routes() {
    return RouterFunctions
            .route(GET("/hello"), req ->
                    ServerResponse.ok().bodyValue(String.format("hello world. %s", req.queryParam("name").orElse("anonymous"))));
}

悪くないですね!!ラムダ式周りで結構書きやすいです

KotlinでのSpring WebfluxのRouter

@Bean
fun routes() = router {
    GET("/hello") {
        ServerResponse.ok().bodyValue("hello world, ${it.queryParam("name").orElse("anonymous")}")
    }
}

Kotlinの場合はdslで書けるので、少しシンプルに見えますね!

KotlinでのSpring WebfluxのRouter(Coroutineを使うパターン)

CoRouterFunctionDsl.GETsuspend の関数で ServerResponse を返却することを求めていますので、 ServerResponse.bodyValueAndAwait を利用します。

@Bean
fun routes() = coRouter {
    GET("/hello") {
        ServerResponse.ok().bodyValueAndAwait("hello world, ${it.queryParam("name").orElse("anonymous")}")
    }
}

なんとなくKotlinのDSLが読みやすいのはわかりますね。

複数の要素を返す場合

上記は要素一つを返す例でした。次は要素を複数返す場合についてです。

Javaでの場合

Fluxっぽく無限stream要素をから、1秒ごとに一つづつ返すものとします
JavaではMonoではなくFluxを返すことになります(bodyValueがMonoを返していた)

@Bean
public RouterFunction<ServerResponse> routes() {
    return RouterFunctions
            .route(GET("/hello"), req -> 
                    ServerResponse.ok()
                            .contentType(MediaType.APPLICATION_STREAM_JSON)
                            .body(
                                    Flux.fromStream(Stream.iterate(0, i -> i + 1))
                                            .delayElements(Duration.ofSeconds(1)),
                                    Integer.class
                            )
            );
}

Kotlinでの場合

KotlinではFlowを返します。
基本はListからFlowにします(無限ストリームを利用するのでsequenceから作成しています

@Bean
fun routes() = coRouter {
    GET("/hello") {
        ServerResponse.ok()
            .contentType(MediaType.APPLICATION_STREAM_JSON)
            .bodyAndAwait(flow {
                generateSequence(0) { it + 1 }.asFlow().collect {
                    delay(1000)
                    emit(it)
                }
            })
    }
}

リクエストを投げるとJavaのときと同じようにが返却されているのがわかります。

curl -v localhost:8080/hello -H 'Accept: application/stream+json'
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /hellok HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: application/stream+json
> 
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/stream+json
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Content-Type-Options: nosniff
< X-Frame-Options: DENY
< X-XSS-Protection: 1 ; mode=block
< Referrer-Policy: no-referrer
< 
0
1
2
3
4
5
6
7

考察

はてさて、Kotlinを使うということDSLだと短くきれいに書けますね。っていうのは良いと思います
ではcoroutineを使う必要はあるんでしょうか?使いたいんだから使うんだよという気持ち

個人的には、MonoやFluxの(reactor-core)に依存せず、Kotlinの言語にあるcoroutine(suspendやFlow)に依存するところの違いかなぁと思っています。
bodyValueAndAwaitやbodyAndAwaitは内部で reactive-stream には依存していますが、reactor-core というライブラリの実装に依存していない感じです。

詳しくは以下の通り書いてくださっているので、こちらも合わせて参照してもらえると良いのかなぁと思います
https://github.com/pljp/kotlinx.coroutines/blob/japanese_translation/reactive/coroutines-guide-reactive.md

まとめ

Kotlin書きやすいですね、読みやすいですし。
参考資料などはあまりなく、開発時は色々辛いこと(例外がどこで起きたのか全然追えないなど)もありましたが
現在はうまく使えていますし、また今回WebFluxと合わせることで、Coroutineのことが若干わかった気がしました。

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6