web-dev-qa-db-fra.com

Comment vérifier si Mono est vide?

Je développe une application avec Spring Boot 2.0 et Kotlin en utilisant le framework WebFlux.

Je veux vérifier si un identifiant utilisateur se termine avant d'enregistrer une transaction. Je suis coincé dans une chose simple comme valider si un Mono est vide.

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()) }
}

Est-il possible de faire ce que je veux?

14
voliveira89

Les techniques qui permettent de vérifier si Flux/Mono est vide

Utilisation des opérateurs .switchIfEmpty/.defaultIfEmpty/Mono.repeatWhenEmpty

En utilisant les opérateurs mentionnés, vous pourrez réagir au cas où Stream sera terminé sans émettre aucun élément.

Tout d'abord, rappelez-vous que les opérateurs tels que .map, .flatMap, .filter Et bien d'autres ne seront pas du tout appelés si aucun onNext n'a été invoqué. Cela signifie que dans votre cas, le code suivant

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()) }

ne sera pas invoqué du tout, si transaction sera vide.

Dans le cas où il est nécessaire de gérer les cas lorsque votre flux est vide, vous devez considérer les opérateurs comme Next de la manière suivante:

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

Solution réelle

J'ai également remarqué que vous avez créé deux sous-flux à partir du transaction principal. En fait, le code suivant ne sera pas exécuté du tout:

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

et ne sera exécuté que le dernier, qui est renvoyé par la méthode. Cela se produit car vous n'êtes pas abonné à l'aide de l'opérateur .subscribe(...).

Le deuxième point, vous ne pouvez pas vous abonner au même corps de demande plus d'une fois (type de limitation pour la réponse de WebClient). Ainsi, vous êtes tenu de partager votre corps de demande de la manière suivante, donc l'exemple complété sera:

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) })
}

Ou cas plus simple sans partager le flux de transactions mais en utilisant 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

Permettez-moi de commencer en disant que je suis un débutant sur réactif (Java) et sur ce forum. Je pense que vous ne pouvez pas vraiment vérifier dans ce code si un mono est vide car un mono représente du code qui sera exécuté plus tard, donc dans ce corps de code, vous ne saurez pas encore s'il est vide. Cela a-t-il du sens?

Je viens d'écrire quelque chose de similaire en Java qui semble fonctionner (mais pas à 100% c'est la meilleure approche non plus)):

    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