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