SlideShare a Scribd company logo
1 of 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のエラーハンドリング」

More Related Content

What's hot

第三回ありえる社内勉強会 「いわががのLombok」
第三回ありえる社内勉強会 「いわががのLombok」第三回ありえる社内勉強会 「いわががのLombok」
第三回ありえる社内勉強会 「いわががのLombok」yoshiaki iwanaga
 
これで怖くない!?コードリーディングで学ぶSpring Security #中央線Meetup
これで怖くない!?コードリーディングで学ぶSpring Security #中央線Meetupこれで怖くない!?コードリーディングで学ぶSpring Security #中央線Meetup
これで怖くない!?コードリーディングで学ぶSpring Security #中央線MeetupMasatoshi Tada
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean ArchitectureAtsushi Nakamura
 
導入から 10 年、PHP の trait は滅びるべきなのか その適切な使いどころと弱点、将来について
導入から 10 年、PHP の trait は滅びるべきなのか その適切な使いどころと弱点、将来について導入から 10 年、PHP の trait は滅びるべきなのか その適切な使いどころと弱点、将来について
導入から 10 年、PHP の trait は滅びるべきなのか その適切な使いどころと弱点、将来についてshinjiigarashi
 
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)NTT DATA Technology & Innovation
 
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところY Watanabe
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!mosa siru
 
コンテナ環境でJavaイメージを小さくする方法!
コンテナ環境でJavaイメージを小さくする方法!コンテナ環境でJavaイメージを小さくする方法!
コンテナ環境でJavaイメージを小さくする方法!オラクルエンジニア通信
 
こんなに使える!今どきのAPIドキュメンテーションツール
こんなに使える!今どきのAPIドキュメンテーションツールこんなに使える!今どきのAPIドキュメンテーションツール
こんなに使える!今どきのAPIドキュメンテーションツールdcubeio
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021Hiroshi Tokumaru
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪Takuto Wada
 
さくっと理解するSpring bootの仕組み
さくっと理解するSpring bootの仕組みさくっと理解するSpring bootの仕組み
さくっと理解するSpring bootの仕組みTakeshi Ogawa
 
Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方Taku Miyakawa
 
今こそ知りたいSpring Web(Spring Fest 2020講演資料)
今こそ知りたいSpring Web(Spring Fest 2020講演資料)今こそ知りたいSpring Web(Spring Fest 2020講演資料)
今こそ知りたいSpring Web(Spring Fest 2020講演資料)NTT DATA Technology & Innovation
 
Keycloak拡張入門
Keycloak拡張入門Keycloak拡張入門
Keycloak拡張入門Hiroyuki Wada
 
マイクロサービス 4つの分割アプローチ
マイクロサービス 4つの分割アプローチマイクロサービス 4つの分割アプローチ
マイクロサービス 4つの分割アプローチ増田 亨
 
初めてでも30分で分かるSpring 5 & Spring Boot 2オーバービュー
初めてでも30分で分かるSpring 5 & Spring Boot 2オーバービュー初めてでも30分で分かるSpring 5 & Spring Boot 2オーバービュー
初めてでも30分で分かるSpring 5 & Spring Boot 2オーバービューMasatoshi Tada
 
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -onozaty
 

What's hot (20)

第三回ありえる社内勉強会 「いわががのLombok」
第三回ありえる社内勉強会 「いわががのLombok」第三回ありえる社内勉強会 「いわががのLombok」
第三回ありえる社内勉強会 「いわががのLombok」
 
これで怖くない!?コードリーディングで学ぶSpring Security #中央線Meetup
これで怖くない!?コードリーディングで学ぶSpring Security #中央線Meetupこれで怖くない!?コードリーディングで学ぶSpring Security #中央線Meetup
これで怖くない!?コードリーディングで学ぶSpring Security #中央線Meetup
 
世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture世界一わかりやすいClean Architecture
世界一わかりやすいClean Architecture
 
導入から 10 年、PHP の trait は滅びるべきなのか その適切な使いどころと弱点、将来について
導入から 10 年、PHP の trait は滅びるべきなのか その適切な使いどころと弱点、将来について導入から 10 年、PHP の trait は滅びるべきなのか その適切な使いどころと弱点、将来について
導入から 10 年、PHP の trait は滅びるべきなのか その適切な使いどころと弱点、将来について
 
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
今こそ知りたいSpring Batch(Spring Fest 2020講演資料)
 
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
ツール比較しながら語る O/RマッパーとDBマイグレーションの実際のところ
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
 
コンテナ環境でJavaイメージを小さくする方法!
コンテナ環境でJavaイメージを小さくする方法!コンテナ環境でJavaイメージを小さくする方法!
コンテナ環境でJavaイメージを小さくする方法!
 
こんなに使える!今どきのAPIドキュメンテーションツール
こんなに使える!今どきのAPIドキュメンテーションツールこんなに使える!今どきのAPIドキュメンテーションツール
こんなに使える!今どきのAPIドキュメンテーションツール
 
SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021SPAセキュリティ入門~PHP Conference Japan 2021
SPAセキュリティ入門~PHP Conference Japan 2021
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
 
さくっと理解するSpring bootの仕組み
さくっと理解するSpring bootの仕組みさくっと理解するSpring bootの仕組み
さくっと理解するSpring bootの仕組み
 
Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方
 
今こそ知りたいSpring Web(Spring Fest 2020講演資料)
今こそ知りたいSpring Web(Spring Fest 2020講演資料)今こそ知りたいSpring Web(Spring Fest 2020講演資料)
今こそ知りたいSpring Web(Spring Fest 2020講演資料)
 
Railsで作るBFFの功罪
Railsで作るBFFの功罪Railsで作るBFFの功罪
Railsで作るBFFの功罪
 
Keycloak拡張入門
Keycloak拡張入門Keycloak拡張入門
Keycloak拡張入門
 
マイクロサービス 4つの分割アプローチ
マイクロサービス 4つの分割アプローチマイクロサービス 4つの分割アプローチ
マイクロサービス 4つの分割アプローチ
 
初めてでも30分で分かるSpring 5 & Spring Boot 2オーバービュー
初めてでも30分で分かるSpring 5 & Spring Boot 2オーバービュー初めてでも30分で分かるSpring 5 & Spring Boot 2オーバービュー
初めてでも30分で分かるSpring 5 & Spring Boot 2オーバービュー
 
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
今からでも遅くないDBマイグレーション - Flyway と SchemaSpy の紹介 -
 
Serverless時代のJavaについて
Serverless時代のJavaについてServerless時代のJavaについて
Serverless時代のJavaについて
 

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のエラーハンドリング」

Editor's Notes

  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 ができそうという話でした。 スライドとソースは公開するので、気になった人はじっくり見てみてください。