SlideShare une entreprise Scribd logo
1  sur  21
Spring WebFluxを
簡単に書きたい
前多(twitter @kencharos)
JJUG CCC 2018 Fall LT
About Me
• 設計と実装してる人
• Java ときどき C#
• 最近
• 「GraalVM の native image を使って Java で爆速
Lambda の夢を見る」
• https://qiita.com/kencharos/items/69e43965515f368bc4a3
WebFlux の使いどころ
• 複数の非同期処理のフローを簡単に記述できる
• 例えば 3つの WebAPI 呼び出しを順番に呼び出
したり、一部を並行(同時)にしたり、、
“hello” “world” “!!!"
“hello”
“world”
“!!!”
//WebAPI 呼び出しの例
public Mono<String> callApi() {
return WebClient.create("http://xxxxx/hello")
.get().retrieve().bodyToMono(String.class);
}
APIを逐次実行にする例
• flatMap で3つのMonoを順次実行
@GetMapping("/java/rx_seq")
public Mono<String> Seq() { // 逐次実行
Mono<String> hello = helloApi.callApi();
Mono<String> world = worldApi.callApi();
Mono<String> exclamation = exclamationApi.callApi();
return hello
.flatMap(h -> world
.flatMap(w -> exclamation
.map(e -> h + w + e)));
}
APIの一部を並行にする例
• Zip で2つのMonoを待ち合わせして、その後
flatMap
@GetMapping("/java/rx_seq")
public Mono<String> Par() { // 一部並列実行
Mono<String> hello = helloApi.callApi();
Mono<String> world = worldApi.callApi();
Mono<String> exclamation = exclamationApi.callApi();
return Mono.zip(hello, world)
.flatMap(hw -> exclamation
.map(e -> hw.getT1() + hw.getT2() + e));
}
本当に簡単?
• スレッドとかブロッキングとかを考えなくてい
いので楽
• @Async とか使わなくていいので楽
• 非同期処理の待ち合わせも楽
• とはいえ、従来のプログラムの書き方とは結構
違う
• flatMap がネストしだすと少々見づらい
• もっと楽にできないか??
Kotlin のコルーチンを使う
• コルーチンは処理を途中で止めたり再開したり
する機能
• Java だと Project Loom の Fiberで入るかも?
• コルーチンはKotlin1.3 から標準化
• Spring5 から Kotlin 正式対応
• Reactor のコルーチン対応ライブラリ
kotlinx-coroutines-reactor がある
コルーチンで逐次処理の例
• mono スコープと awaitXxx を使うと
複数のMonoを一つに結合できる
@GetMapping("seq")
fun seqWithCoroutine():Mono<String> = GlobalScope.mono {
// awaitFirst で、 Monoを コルーチンに
val hello: String = helloApi.callApi().awaitFirst()
val world: String = worldApi.callApi().awaitFirst()
val exclamation: String = exclamationApi.callApi().awaitFirst()
"$hello $world $exclamation"
}
monoスコープ
で囲む
MonoにawaitFirstでコルーチン化。
Monoの中の結果のString を取得で
きる
この値(3 APIの呼び出し結果の連結)が
最終的な戻り値になる
コルーチンで並行化の例
• asyncで一部を非同期実行できる
@GetMapping("par")
fun parWithCoroutine():Mono<String> = GlobalScope.mono {
// start helloApi and worldApi tasks in asynchronous.
val helloDeferred = async { helloApi.callApi().awaitFirst() }
val worldDeferred = async { worldApi.callApi().awaitFirst() }
// join 2 tasks
val hello: String = helloDeferred.await()
val world: String = worldDeferred.await()
val exclamation = exclamationApi.callApi().awaitFirst()
"$hello $world $exclamation"
}
async スコープ
で非同期処理
開始
非同期処理の
結果は await
で取得
コルーチンの Pros/Cons
• Pros
• 同期処理っぽく書ける(flatMapが消えた)
• 通常の kotlinの制御構文が使える(if, for, try/catch)
• JavaScript/C# の async/await に近い感じ
• async でカジュアルに一部を非同期処理にできる
• Cons
• awaitの呼び出しごとに Rxの subscribe が実行されて
いる
• Rx の文脈をコルーチンの非同期処理に置き換えてしまう
• 他には?
Arrowライブラリを使う
• https://arrow-kt.io/
• Scala の関数型ライブラリ Scalaz, Cats のような
関数型プログラミングの機能(モナドとか)を
Kotlinに持ち込む
• Reactor 用の拡張を使うと、 Mono, Flux を
MonoK, FluxK に変換して モナドにできる
Arrowで逐次処理の例
• 大体の記述はコルーチンと似ている
• bindingスコープ(モナド記法), k(), bind() を使う
@GetMapping("monad_seq")
fun seqWithMonad():Mono<String> = MonoK.monad().binding {
// K() is buidler from Mono to MonoK. MonoK is Monad of Mono in Arrow.
// bind() is flatMap as Coroutine.
val hello: String = helloApi.callApi().k().bind()
val world: String = worldApi.callApi().k().bind()
val exclamation: String = exclamationApi.callApi().k().bind()
"$hello $world $exclamation"
}.value()
K()でMonoを
MonoKに
value()で
MonoKをMono
に戻す
最終的な
戻り値
bind() でMono
の中の String
を取得できる
Arrow で並行処理
• Mono.zip で待ち合わせしたMonoをbindする
• Arrowだけで 並行化する方法を調査中
@GetMapping(“monad_par”)
fun parWithMonad():Mono<String> = MonoK.monad().binding {
val hMono = helloApi.callApi()
val wMono = worldApi.callApi()
// 並列化は Mono zip を使うしかないっぽい
val helloWorld = Mono.zip(hMono, wMono).map { it.t1 + it.t2 }.k().bind()
val exclamation = exclamationApi.callApi().k().bind()
"$helloWorld $exclamation"
}.value()
ArrowのPros/Cons
• Pros
• bindを使って、同期処理っぽく書ける
• bind 呼び出しは flatMap の呼び出しに置き換わるの
で Rx の文脈は保たれる
• Arrowの他の機能との統合(後述)
• モナド完全に理解した
• Cons
• モナド全然わからん
• 関数型プログラミングの理解が必須
• Mono.zip の代替手段が(今のところ)なさそう
• アプリカティブなだけの Monoがなさそう
ここまでまとめ
• Kotlinなら WebFlux が使いやすくなるかもよ
• バックプレッシャー周りは継続調査
(おまけ) Arrow の Either
• Either は成功か失敗かのどちらかの表現
• 例外の代わりに使うことが多い
• ArrowのEither はモナドなので合成可能
fun aToI(s:String):Either<NumberFormatException, Int> {
if (s.matches("^[0-9]+$".toRegex())) {
return Either.right(Integer.parseInt(s))
} else {
return Either.left(NumberFormatException("invalid string"))
}
}
val result = aToI("123")
when(result) {
is Either.Right -> print("OK " + result.b)
is Either.Left -> print("NG" + result.a.message)
}
Right(成功)
Left(失敗)
MonoKとEitherが組み合わさると
• 非同期処理の結果に Either を使って失敗の可能
性を表現したい場合、Mono(K)とEither を組み
合わせた型ができる
fun callApiOrError(name:String):MonoK<Either<Throwable, String>> {///}
モナドのネストは見づらい
• MonoKのbindでEither が取れてしまう
• EitherからStringを取るとモナドがネストする
val resEither = MonoK.monad().binding {
val helloEither:Either<Throwable, String> =
helloApi.callApiOrError(query).bind()
val worldEither = worldApi.callApiOrError(query).bind()
val exclamationEither = exclamationApi.callApiOrError(query).bind()
// モナドにモナドがダブってしまう
Either.monadError<Throwable>().binding {
val hello:String = helloEither.bind()
val world:String = worldEither.bind()
val exclamation:String = exclamationEither.bind()
"$hello $world $exclamation"
}.fix()
}.value()
EitherT で MonoK<Either> を合体する
• EitherT (モナドトランスフォーマー)を使う
と、モナドのネストが消え、従来通りにかける
• bind でMono, Eitherの両方を一発ではがす
• bindがどこかで失敗(=Either.Leftの発生)するとその
場で評価を停止し、MonoK(Either.Left)を返す
• 全て成功した場合は、MonoK(Either.Right)を返す
val result = EitherT.monad<ForMonoK, Throwable>(MonoK.monad()).binding {
val hello:String = EitherT(helloApi.callApiOrError(query)).bind()
val world:String = EitherT(worldApi.callApiOrError(query)).bind()
val exclamation = EitherT(exclamationApi.callApiOrError(query)).bind()
"$hello $world $exclamation"
}
最終的な変換処理は必要
• 最終的に Mono<Either<T>> をちゃんと変換する
コードは必要
• 機械的に書ける内容なので、 Mono<Either<T>> をコ
ントローラの戻り値にして、Spring 側でハンドリン
グするようにできるはず
val res:Mono<Either<Throwable, String>> = result.fix().value().value()
return res.map { when(it){
is Either.Right -> ResponseEntity.ok().body(it.b)
is Either.Left -> ResponseEntity.badRequest().body(it.a.message)
} }
おしまい
• Kotlin + Arrow で Functional Reactive Spring の夢
が見よう!
• ソース
• https://github.com/kencharos/webflux-coroutine-
arrow-sample
• Arrow の Either についてはアドカレ書いてます
• https://qiita.com/kencharos/items/6fd0a9e92363b08c0340
• 「例外だけに頼らない Kotlinのエラーハンドリング」

Contenu connexe

Tendances

【Unite Tokyo 2019】Understanding C# Struct All Things
【Unite Tokyo 2019】Understanding C# Struct All Things【Unite Tokyo 2019】Understanding C# Struct All Things
【Unite Tokyo 2019】Understanding C# Struct All ThingsUnityTechnologiesJapan002
 
Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編Masahito Zembutsu
 
それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?Yoshitaka Kawashima
 
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践Yoshifumi Kawai
 
リアルタイムサーバー 〜Erlang/OTPで作るPubSubサーバー〜
リアルタイムサーバー 〜Erlang/OTPで作るPubSubサーバー〜 リアルタイムサーバー 〜Erlang/OTPで作るPubSubサーバー〜
リアルタイムサーバー 〜Erlang/OTPで作るPubSubサーバー〜 Yugo Shimizu
 
今こそ知りたいSpring Web(Spring Fest 2020講演資料)
今こそ知りたいSpring Web(Spring Fest 2020講演資料)今こそ知りたいSpring Web(Spring Fest 2020講演資料)
今こそ知りたいSpring Web(Spring Fest 2020講演資料)NTT DATA Technology & Innovation
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean ArchitectureAtsushi Nakamura
 
テスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなテスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなKentaro Matsui
 
MagicOnion入門
MagicOnion入門MagicOnion入門
MagicOnion入門torisoup
 
シリコンバレーの「何が」凄いのか
シリコンバレーの「何が」凄いのかシリコンバレーの「何が」凄いのか
シリコンバレーの「何が」凄いのかAtsushi Nakada
 
Riverpodでテストを書こう
Riverpodでテストを書こうRiverpodでテストを書こう
Riverpodでテストを書こうShinnosuke Tokuda
 
Using or not using magic onion
Using or not using magic onionUsing or not using magic onion
Using or not using magic onionGoichi Shinohara
 
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現Yoshifumi Kawai
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」Takuto Wada
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪Takuto Wada
 
ホットペッパービューティーにおけるモバイルアプリ向けAPIのBFF/Backend分割
ホットペッパービューティーにおけるモバイルアプリ向けAPIのBFF/Backend分割ホットペッパービューティーにおけるモバイルアプリ向けAPIのBFF/Backend分割
ホットペッパービューティーにおけるモバイルアプリ向けAPIのBFF/Backend分割Recruit Lifestyle Co., Ltd.
 
エンジニアから飛んでくるマサカリを受け止める心得
エンジニアから飛んでくるマサカリを受け止める心得エンジニアから飛んでくるマサカリを受け止める心得
エンジニアから飛んでくるマサカリを受け止める心得Reimi Kuramochi Chiba
 

Tendances (20)

【Unite Tokyo 2019】Understanding C# Struct All Things
【Unite Tokyo 2019】Understanding C# Struct All Things【Unite Tokyo 2019】Understanding C# Struct All Things
【Unite Tokyo 2019】Understanding C# Struct All Things
 
Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編Dockerfile を書くためのベストプラクティス解説編
Dockerfile を書くためのベストプラクティス解説編
 
それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?それはYAGNIか? それとも思考停止か?
それはYAGNIか? それとも思考停止か?
 
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践
「黒騎士と白の魔王」gRPCによるHTTP/2 - API, Streamingの実践
 
リアルタイムサーバー 〜Erlang/OTPで作るPubSubサーバー〜
リアルタイムサーバー 〜Erlang/OTPで作るPubSubサーバー〜 リアルタイムサーバー 〜Erlang/OTPで作るPubSubサーバー〜
リアルタイムサーバー 〜Erlang/OTPで作るPubSubサーバー〜
 
今こそ知りたいSpring Web(Spring Fest 2020講演資料)
今こそ知りたいSpring Web(Spring Fest 2020講演資料)今こそ知りたいSpring Web(Spring Fest 2020講演資料)
今こそ知りたいSpring Web(Spring Fest 2020講演資料)
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture
 
WebSocket / WebRTCの技術紹介
WebSocket / WebRTCの技術紹介WebSocket / WebRTCの技術紹介
WebSocket / WebRTCの技術紹介
 
テスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるなテスト文字列に「うんこ」と入れるな
テスト文字列に「うんこ」と入れるな
 
MagicOnion入門
MagicOnion入門MagicOnion入門
MagicOnion入門
 
シリコンバレーの「何が」凄いのか
シリコンバレーの「何が」凄いのかシリコンバレーの「何が」凄いのか
シリコンバレーの「何が」凄いのか
 
Riverpodでテストを書こう
Riverpodでテストを書こうRiverpodでテストを書こう
Riverpodでテストを書こう
 
Using or not using magic onion
Using or not using magic onionUsing or not using magic onion
Using or not using magic onion
 
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
 
Nginx lua
Nginx luaNginx lua
Nginx lua
 
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
 
ホットペッパービューティーにおけるモバイルアプリ向けAPIのBFF/Backend分割
ホットペッパービューティーにおけるモバイルアプリ向けAPIのBFF/Backend分割ホットペッパービューティーにおけるモバイルアプリ向けAPIのBFF/Backend分割
ホットペッパービューティーにおけるモバイルアプリ向けAPIのBFF/Backend分割
 
TLS, HTTP/2演習
TLS, HTTP/2演習TLS, HTTP/2演習
TLS, HTTP/2演習
 
エンジニアから飛んでくるマサカリを受け止める心得
エンジニアから飛んでくるマサカリを受け止める心得エンジニアから飛んでくるマサカリを受け止める心得
エンジニアから飛んでくるマサカリを受け止める心得
 

Writing Spring WebFlux more esay with kotlin

  • 2. About Me • 設計と実装してる人 • Java ときどき C# • 最近 • 「GraalVM の native image を使って Java で爆速 Lambda の夢を見る」 • https://qiita.com/kencharos/items/69e43965515f368bc4a3
  • 3. WebFlux の使いどころ • 複数の非同期処理のフローを簡単に記述できる • 例えば 3つの WebAPI 呼び出しを順番に呼び出 したり、一部を並行(同時)にしたり、、 “hello” “world” “!!!" “hello” “world” “!!!” //WebAPI 呼び出しの例 public Mono<String> callApi() { return WebClient.create("http://xxxxx/hello") .get().retrieve().bodyToMono(String.class); }
  • 4. APIを逐次実行にする例 • flatMap で3つのMonoを順次実行 @GetMapping("/java/rx_seq") public Mono<String> Seq() { // 逐次実行 Mono<String> hello = helloApi.callApi(); Mono<String> world = worldApi.callApi(); Mono<String> exclamation = exclamationApi.callApi(); return hello .flatMap(h -> world .flatMap(w -> exclamation .map(e -> h + w + e))); }
  • 5. APIの一部を並行にする例 • Zip で2つのMonoを待ち合わせして、その後 flatMap @GetMapping("/java/rx_seq") public Mono<String> Par() { // 一部並列実行 Mono<String> hello = helloApi.callApi(); Mono<String> world = worldApi.callApi(); Mono<String> exclamation = exclamationApi.callApi(); return Mono.zip(hello, world) .flatMap(hw -> exclamation .map(e -> hw.getT1() + hw.getT2() + e)); }
  • 6. 本当に簡単? • スレッドとかブロッキングとかを考えなくてい いので楽 • @Async とか使わなくていいので楽 • 非同期処理の待ち合わせも楽 • とはいえ、従来のプログラムの書き方とは結構 違う • flatMap がネストしだすと少々見づらい • もっと楽にできないか??
  • 7. Kotlin のコルーチンを使う • コルーチンは処理を途中で止めたり再開したり する機能 • Java だと Project Loom の Fiberで入るかも? • コルーチンはKotlin1.3 から標準化 • Spring5 から Kotlin 正式対応 • Reactor のコルーチン対応ライブラリ kotlinx-coroutines-reactor がある
  • 8. コルーチンで逐次処理の例 • mono スコープと awaitXxx を使うと 複数のMonoを一つに結合できる @GetMapping("seq") fun seqWithCoroutine():Mono<String> = GlobalScope.mono { // awaitFirst で、 Monoを コルーチンに val hello: String = helloApi.callApi().awaitFirst() val world: String = worldApi.callApi().awaitFirst() val exclamation: String = exclamationApi.callApi().awaitFirst() "$hello $world $exclamation" } monoスコープ で囲む MonoにawaitFirstでコルーチン化。 Monoの中の結果のString を取得で きる この値(3 APIの呼び出し結果の連結)が 最終的な戻り値になる
  • 9. コルーチンで並行化の例 • asyncで一部を非同期実行できる @GetMapping("par") fun parWithCoroutine():Mono<String> = GlobalScope.mono { // start helloApi and worldApi tasks in asynchronous. val helloDeferred = async { helloApi.callApi().awaitFirst() } val worldDeferred = async { worldApi.callApi().awaitFirst() } // join 2 tasks val hello: String = helloDeferred.await() val world: String = worldDeferred.await() val exclamation = exclamationApi.callApi().awaitFirst() "$hello $world $exclamation" } async スコープ で非同期処理 開始 非同期処理の 結果は await で取得
  • 10. コルーチンの Pros/Cons • Pros • 同期処理っぽく書ける(flatMapが消えた) • 通常の kotlinの制御構文が使える(if, for, try/catch) • JavaScript/C# の async/await に近い感じ • async でカジュアルに一部を非同期処理にできる • Cons • awaitの呼び出しごとに Rxの subscribe が実行されて いる • Rx の文脈をコルーチンの非同期処理に置き換えてしまう • 他には?
  • 11. Arrowライブラリを使う • https://arrow-kt.io/ • Scala の関数型ライブラリ Scalaz, Cats のような 関数型プログラミングの機能(モナドとか)を Kotlinに持ち込む • Reactor 用の拡張を使うと、 Mono, Flux を MonoK, FluxK に変換して モナドにできる
  • 12. Arrowで逐次処理の例 • 大体の記述はコルーチンと似ている • bindingスコープ(モナド記法), k(), bind() を使う @GetMapping("monad_seq") fun seqWithMonad():Mono<String> = MonoK.monad().binding { // K() is buidler from Mono to MonoK. MonoK is Monad of Mono in Arrow. // bind() is flatMap as Coroutine. val hello: String = helloApi.callApi().k().bind() val world: String = worldApi.callApi().k().bind() val exclamation: String = exclamationApi.callApi().k().bind() "$hello $world $exclamation" }.value() K()でMonoを MonoKに value()で MonoKをMono に戻す 最終的な 戻り値 bind() でMono の中の String を取得できる
  • 13. Arrow で並行処理 • Mono.zip で待ち合わせしたMonoをbindする • Arrowだけで 並行化する方法を調査中 @GetMapping(“monad_par”) fun parWithMonad():Mono<String> = MonoK.monad().binding { val hMono = helloApi.callApi() val wMono = worldApi.callApi() // 並列化は Mono zip を使うしかないっぽい val helloWorld = Mono.zip(hMono, wMono).map { it.t1 + it.t2 }.k().bind() val exclamation = exclamationApi.callApi().k().bind() "$helloWorld $exclamation" }.value()
  • 14. ArrowのPros/Cons • Pros • bindを使って、同期処理っぽく書ける • bind 呼び出しは flatMap の呼び出しに置き換わるの で Rx の文脈は保たれる • Arrowの他の機能との統合(後述) • モナド完全に理解した • Cons • モナド全然わからん • 関数型プログラミングの理解が必須 • Mono.zip の代替手段が(今のところ)なさそう • アプリカティブなだけの Monoがなさそう
  • 15. ここまでまとめ • Kotlinなら WebFlux が使いやすくなるかもよ • バックプレッシャー周りは継続調査
  • 16. (おまけ) Arrow の Either • Either は成功か失敗かのどちらかの表現 • 例外の代わりに使うことが多い • ArrowのEither はモナドなので合成可能 fun aToI(s:String):Either<NumberFormatException, Int> { if (s.matches("^[0-9]+$".toRegex())) { return Either.right(Integer.parseInt(s)) } else { return Either.left(NumberFormatException("invalid string")) } } val result = aToI("123") when(result) { is Either.Right -> print("OK " + result.b) is Either.Left -> print("NG" + result.a.message) } Right(成功) Left(失敗)
  • 17. MonoKとEitherが組み合わさると • 非同期処理の結果に Either を使って失敗の可能 性を表現したい場合、Mono(K)とEither を組み 合わせた型ができる fun callApiOrError(name:String):MonoK<Either<Throwable, String>> {///}
  • 18. モナドのネストは見づらい • MonoKのbindでEither が取れてしまう • EitherからStringを取るとモナドがネストする val resEither = MonoK.monad().binding { val helloEither:Either<Throwable, String> = helloApi.callApiOrError(query).bind() val worldEither = worldApi.callApiOrError(query).bind() val exclamationEither = exclamationApi.callApiOrError(query).bind() // モナドにモナドがダブってしまう Either.monadError<Throwable>().binding { val hello:String = helloEither.bind() val world:String = worldEither.bind() val exclamation:String = exclamationEither.bind() "$hello $world $exclamation" }.fix() }.value()
  • 19. EitherT で MonoK<Either> を合体する • EitherT (モナドトランスフォーマー)を使う と、モナドのネストが消え、従来通りにかける • bind でMono, Eitherの両方を一発ではがす • bindがどこかで失敗(=Either.Leftの発生)するとその 場で評価を停止し、MonoK(Either.Left)を返す • 全て成功した場合は、MonoK(Either.Right)を返す val result = EitherT.monad<ForMonoK, Throwable>(MonoK.monad()).binding { val hello:String = EitherT(helloApi.callApiOrError(query)).bind() val world:String = EitherT(worldApi.callApiOrError(query)).bind() val exclamation = EitherT(exclamationApi.callApiOrError(query)).bind() "$hello $world $exclamation" }
  • 20. 最終的な変換処理は必要 • 最終的に Mono<Either<T>> をちゃんと変換する コードは必要 • 機械的に書ける内容なので、 Mono<Either<T>> をコ ントローラの戻り値にして、Spring 側でハンドリン グするようにできるはず val res:Mono<Either<Throwable, String>> = result.fix().value().value() return res.map { when(it){ is Either.Right -> ResponseEntity.ok().body(it.b) is Either.Left -> ResponseEntity.badRequest().body(it.a.message) } }
  • 21. おしまい • Kotlin + Arrow で Functional Reactive Spring の夢 が見よう! • ソース • https://github.com/kencharos/webflux-coroutine- arrow-sample • Arrow の Either についてはアドカレ書いてます • https://qiita.com/kencharos/items/6fd0a9e92363b08c0340 • 「例外だけに頼らない Kotlinのエラーハンドリング」

Notes de l'éditeur

  1. Spring Fluxの使いどころはMonoやFluxで表す非同期処理が複数ある場合、そのつなぎ方逐次や同時など色々書けることだと思います。
  2. 3つの非同期処理を順番に呼び出す場合、flatMapでつなげます。
  3. 一部を並列化したい場合は、zipで二つのMonoをくっつけます
  4. Reactorならスレッドとかasyncとか使わなくていいので、楽だとは思いますが、flatMap慣れないとつらいですよね。もっと簡単に書けないでしょうか
  5. で、唐突にKotlinが出てきます。Kotlinのコルーチンは処理の停止・再開を行う機能で非同期処理の代わりに使えます。 Reactor 拡張があるのでこれを使ってみます。
  6. Monoを順番に呼び出す場合は、スコープのなかでawait を使います。Await を使うとMonoの中にあるString が取れるのでそれを連結した値が、最終的なMonoになります。
  7. 並列化したい場所、並列にしたい部分を async で囲んで、await で取得すればOKです。
  8. コルーチン使うと、JSのasyc.await みたいな同期的な書き方ができます。Try-catchもできます。 一方で、awaitするごとに Rxのsubscribe 読んでるみたいなので、Rxのやり方を壊してる感じがします。他の方法も見てみます。
  9. Kotlinには Arrow という関数型のライブラリがあります。いわゆるモナドというやつです。
  10. コルーチンみたいに、スコープの中で、bind を使うと Monoの中のStringが取れます。またbindは flatMap と同じです。
  11. 並列にしたい場合は、 Monoのzipの後に bind しないといけないので、そこは同じです。
  12. Arrow の bindは、flatMapと同じなので、同期処理みたいに書きつつも、Rxとやっていることが同じになります。 モナドすごいと同時に、全然わからんみたいなになりがちなので、関数型プログラムの知識がある程度いります。聞いて下さい。
  13. ここで一旦まとめると、 Kotlinなら WebFlux楽できそう。バックプレッシャーとかは継続して調査という感じです。
  14. 時間が余ってれば、 もう少しArrowの話を。関数型で例外投げるみたいなことをした場合、 Eitherという型を使います。 Rightが成功、Leftが失敗でどちらかだけを持つというデータです。
  15. 失敗するかもしれない、非同期処理の結果を示したくなると、こんな感じで、 MonoとEitherが組み合わさります。
  16. で、これをスコープで合成しようとすると、 bindして取れる値が Either なのでもう一度 スコープ作ってbindしないと Stringが取れないわけです。
  17. ここで登場するのが、モナドトランスフォーマーという機能で、 EitherTというトランスフォーマーで、Mono,Eitherを囲んで bind すると、両方を一気にはがして Stringが取れます。 途中の Eitherが失敗したら、その場でエラーになってくれます。大体今までと同じように書けます。
  18. ただ、最終的に Mono,Either の適切は処理はいるので、そこはフレームワークの内部でうまくやるようにすると、いい感じになるんじゃないかと思ってます。 上みたいに、成功ならOK、失敗なら badRequestにするとかね。
  19. おしまいです。Functional reactive Spring ができそうという話でした。 スライドとソースは公開するので、気になった人はじっくり見てみてください。