web-dev-qa-db-ja.com

Monoが空かどうかを確認する方法は?

WebFluxフレームワークを使用して、Spring Boot 2.0とKotlinでアプリを開発しています。

トランザクションを保存する前に、ユーザーIDが存在するかどうかを確認したいと思います。モノが空かどうかを検証するなどの簡単なことにこだわっています。

fun createTransaction(serverRequest: ServerRequest) : Mono<ServerResponse> {
    val transaction = serverRequest.body(BodyExtractors.toMono(Transaction::class.Java))

    transaction.flatMap {
        val user = userRepository.findById(it.userId)
        // If it's empty, return badRequest() 
    } 

    return transaction.flatMap { transactionRepository.save(it).then(created(URI.create("/transaction/" + it.id)).build()) }
}

私がやりたいことはできますか?

14
voliveira89

Flux/Monoが空かどうかを確認できる手法

演算子を使用する_.switchIfEmpty_/_.defaultIfEmpty_/_Mono.repeatWhenEmpty_

上記の演算子を使用すると、要素を出力せずにStreamが完了した場合に対応できます。

まず、onNextが呼び出されていない場合、_.map_、_.flatMap_、_.filter_などの演算子はまったく呼び出されないことに注意してください。あなたの場合、次のコード

_transaction.flatMap {
    val user = userRepository.findById(it.userId)
    // If it's empty, return badRequest() 
} 

return transaction.flatMap { transactionRepository.save(it).then(created(URI.create("/transaction/" + it.id)).build()) }
_

transactionが空の場合、まったく呼び出されません

フローが空の場合にケースを処理する必要がある場合、次のような演算子を検討する必要があります。

_transaction
   .flatMap(it -> {
      val user = userRepository.findById(it.userId)
   })
   .swithIfEmpty(Flux.defer(() -> Flux.just(badRequest())));
_

実際の解決策

また、メインtransactionから2つのサブフローを作成したことに注意しました。実際、次のコードはまったく実行されません。

_transaction.flatMap {
    val user = userRepository.findById(it.userId)
    // If it's empty, return badRequest() 
}  
_

メソッドから返される最後の1つだけが実行されます。これは、演算子.subscribe(...)を使用してサブスクライブしていないために発生します。

2番目のポイントは、同じリクエスト本文に一度以上サブスクライブすることはできません(WebClientの応答の制限の種類)。したがって、次の方法でリクエスト本文を共有する必要があるため、完成した例は次のようになります。

_fun createTransaction(serverRequest: ServerRequest): Mono<ServerResponse> {
    val transaction = serverRequest.body(BodyExtractors.toMono(Transaction::class.Java)).cache()

    transaction
            .flatMap { userRepository.findById(it.userId) }
            .flatMap { transaction.flatMap { transactionRepository.save(it) } }
            .flatMap { ServerResponse.created(URI.create("/transaction/" + it.id)).build() }
            .switchIfEmpty(transaction.flatMap { ServerResponse.badRequest().syncBody("missed User for transaction " + it.id) })
}
_

または、トランザクションフローを共有せずにTupleを使用するより単純なケース:

_fun createTransaction(serverRequest: ServerRequest): Mono<ServerResponse> {
    val emptyUser = !User()
    val transaction = serverRequest.body<Mono<Transaction>>(BodyExtractors.toMono(Transaction::class.Java))

    transaction
            .flatMap { t ->
                userRepository.findById(t.userId)
                        .map { Tuples.of(t, it) }
                        .defaultIfEmpty(Tuples.of(t, emptyUser))
            }
            .flatMap {
                if (it.t2 != emptyUser) {
                    transactionRepository.save(it.t1)
                            .flatMap { ServerResponse.created(URI.create("/transaction/" + it.id)).build() }
                } else {
                    ServerResponse.badRequest().syncBody("missed User for transaction " + it.t1.id)
                }
            }
}
_
22
Oleh Dokuka

私はリアクティブ(Java)とこのフォーラムの初心者だと言ってみましょう。モノは空の場合、後で実行されるコードを表すため、モノが空の場合、このコードを実際にチェックインできないと思います。それは理にかなっていますか?

私はJavaで動作するように見えますが、100%ではなくこれが最良のアプローチです)で似たようなものを書いたところです。

    public Mono<ServerResponse> queryStore(ServerRequest request) { 

        Optional<String> postalCode = request.queryParam("postalCode");                            

        Mono<ServerResponse> badQuery = ServerResponse.badRequest().build();
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();

        if (!postalCode.isPresent()) { return  badQuery; }

        Flux<Store> stores = this.repository
                .getNearByStores(postalCode.get(), 5);

        return ServerResponse.ok().contentType(APPLICATION_JSON)
                .body(stores, Store.class)
                .switchIfEmpty(notFound);
}
1
Arno