私はプロジェクト Reactor および一般的なリアクティブプログラミングを始めたばかりです。
現在、次のようなコードに取り組んでいます:
Mono.just(userId)
.map(repo::findById)
.map(user-> {
if(user == null){
throw new UserNotFoundException();
}
return user;
})
// ... other mappings
この例はおそらく馬鹿げており、このケースを実装するより良い方法は確かにありますが、ポイントは次のとおりです。
map
ブロックでthrow new
例外を使用するのは間違っていますか、これをreturn Mono.error(new UserNotFoundException())
に置き換える必要がありますか?
これらの2つの方法に実際の違いはありますか?
例外をスローする便利な方法として考えられるいくつかの方法があります。
Flux/Mono.handle
を使用して要素を処理しますエラーまたは空のストリームを引き起こす可能性のある要素の処理を簡素化できる方法の1つは、演算子handle
です。
次のコードは、問題を解決するためにそれを使用する方法を示しています。
Mono.just(userId)
.map(repo::findById)
.handle((user, sink) -> {
if(!isValid(user)){
sink.error(new InvalidUserException());
} else if (isSendable(user))
sink.next(user);
}
else {
//just ignore element
}
})
ご覧のとおり、.handle
演算子は、要素を処理するためにBiConsumer<T, SynchronousSink<>
を渡す必要があります。ここでは、BiConsumerに2つのパラメーターがあります。最初の要素は上流からの要素であり、2番目の要素はSynchronousSink
であり、要素を下流に同期的に供給するのに役立ちます。このような手法により、要素の処理のさまざまな結果を提供する機能が拡張されます。たとえば、要素が無効な場合、同じSycnchronousSync
にエラーを供給して、アップストリームをキャンセルし、ダウンストリームにonError
信号を生成できます。次に、同じhandle
演算子を使用して「フィルタリング」できます。ハンドルBiConsumer
が実行され、要素が指定されていない場合、Reactorはそれを一種のフィルタリングと見なし、追加の要素を要求します。最後に、要素が有効な場合は、SynchronousSink#next
を呼び出して要素をダウンストリームに伝播するか、マッピングを適用するだけで、handle
をmap
演算子として使用できます。さらに、パフォーマンスに影響を与えずにその演算子を安全に使用し、要素の検証やダウンストリームへのエラー送信などの複雑な要素検証を提供できます。
#concatMap
+ Mono.error
を使用してスローしますマッピング中に例外をスローするオプションの1つは、map
をconcatMap
に置き換えることです。本質的に、concatMap
はflatMap
とほぼ同じです。唯一の違いは、concatMap
は一度に1つのサブストリームのみを許可することです。このような動作により、内部実装が大幅に簡素化され、パフォーマンスに影響しません。したがって、より機能的な方法で例外をスローするには、次のコードを使用できます。
Mono.just(userId)
.map(repo::findById)
.concatMap(user-> {
if(!isValid(user)){
return Mono.error(new InvalidUserException());
}
return Mono.just(user);
})
無効なユーザーの場合の上記のサンプルでは、Mono.error
を使用して例外を返します。 Flux.error
を使用して、fluxでも同じことができます。
Flux.just(userId1, userId2, userId3)
.map(repo::findById)
.concatMap(user-> {
if(!isValid(user)){
return Flux.error(new InvalidUserException());
}
return Mono.just(user);
})
Note、どちらの場合も、要素が1つしかないcoldストリームを返します。 Reactorには、返されるストリームがコールドスカラーストリームである場合のパフォーマンスを改善する最適化がいくつかあります。したがって、Flux/Monoを使用することをお勧めしますconcatMap
+ .just
、empty
、error
は、より複雑なマッピングが必要な場合に、結果としてreturn null
またはthrow new ...
。
注意!ヌル可能性について着信エレメントをチェックしないでください。 Reactor Projectは
null
値を送信しません。これはReactive Streamsの仕様に違反しているためです( Rule 2.1 )。したがって、repo.findById
はnullを返し、ReactorはNullPointerExceptionをスローします。
concatMap
はflatMap
よりも優れているのですか?本質的に、flatMap
は、一度に実行されている複数のサブストリームの要素をマージするように設計されています。つまり、flatMapはその下に非同期ストリームを持っている必要があるため、複数のスレッドでデータを処理したり、複数のネットワーク呼び出しを行ったりする可能性があります。その後、このような期待は実装に多大な影響を与えるため、flatMap
は複数のストリーム(Thread
s)(同時データ構造の使用を意味します)からのデータを処理できる必要があります。 (サブストリームごとにQueue
sの追加メモリ割り当てを意味します)、Reactive Streamsの仕様ルールに違反しません(実際に複雑な実装を意味します)。これらすべての事実と、単純なmap
操作(同期)をFlux/Mono.error
(実行の同期性を変更しない)を使用してより便利な例外をスローする方法に置き換えるという事実を数えると、このような複雑な演算子は必要なく、一度に1つのストリームを非同期処理するように設計され、スカラーのコールドストリームを処理するための最適化がいくつか行われている、はるかに単純なconcatMap
を使用できます.
switchOnEmpty
を使用して例外をスローしますしたがって、結果が空のときに例外をスローする別のアプローチは、switchOnEmpty
演算子です。次のコードは、そのアプローチの使用方法を示しています。
Mono.just(userId)
.flatMap(repo::findById)
.switchIfEmpty(Mono.error(new UserNotFoundExeception()))
ご覧のとおり、この場合、repo::findById
には、戻り型としてMono
またはUser
が必要です。したがって、User
インスタンスが見つからない場合、結果ストリームは空になります。したがって、ReactorはMono
パラメーターとして指定された代替switchIfEmpty
を呼び出します。
読みにくいコードまたは悪い習慣としてカウントされる可能性がありますが、例外をそのままスローすることもできます。このパターンはリアクティブストリームの仕様に違反していますが、リアクタはスローされた例外をキャッチし、onError
信号としてダウンストリームに伝播します
.handle
演算子を使用しますconcatMap
+ Mono.error
を使用しますが、このような手法は非同期要素処理の場合に最適です。flatMap
が配置されている場合は、flatMap
+ Mono.error
を使用しますNull
は戻り型として禁止されているため、ダウンストリームでnull
の代わりにmap
を使用すると、onError
で予期しないNullPointerException
を取得できます。switchIfEmpty
を使用します