J'ai deux méthodes.
Méthode principale:
@PostMapping("/login")
public Mono<ResponseEntity<ApiResponseLogin>> loginUser(@RequestBody final LoginUser loginUser) {
return socialService.verifyAccount(loginUser)
.flatMap(socialAccountIsValid -> {
if (socialAccountIsValid) {
return this.userService.getUserByEmail(loginUser.getEmail())
.switchIfEmpty(insertUser(loginUser))
.flatMap(foundUser -> updateUser(loginUser, foundUser))
.map(savedUser -> {
String jwts = jwt.createJwts(savedUser.get_id(), savedUser.getFirstName(), "user");
return new ResponseEntity<>(HttpStatus.OK);
});
} else {
return Mono.just(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
}
});
}
Et cette méthode invoquée (le service appelle une API externe):
public Mono<User> getUserByEmail(String email) {
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(USER_API_BASE_URI)
.queryParam("email", email);
return this.webClient.get()
.uri(builder.toUriString())
.exchange()
.flatMap(resp -> {
if (Integer.valueOf(404).equals(resp.statusCode().value())) {
return Mono.empty();
} else {
return resp.bodyToMono(User.class);
}
});
}
Dans l'exemple ci-dessus, switchIfEmpty()
est toujours appelée à partir de la méthode principale, même lorsqu'un résultat avec Mono.empty()
est renvoyé.
Je ne trouve pas de solution à ce problème simple.
Les éléments suivants ne fonctionnent pas non plus:
Mono.just(null)
Parce que la méthode lèvera une exception nullpointerexception.
Ce que je ne peux pas non plus utiliser, c'est la méthode flatMap pour vérifier que foundUser
est nul.
Malheureusement, flatMap n'est pas appelé du tout si je retourne Mono.empty()
, donc je ne peux pas ajouter de condition ici non plus.
Toute aide est appréciée.
@ SimY4
@PostMapping("/login")
public Mono<ResponseEntity<ApiResponseLogin>> loginUser(@RequestBody final LoginUser loginUser) {
userExists = false;
return socialService.verifyAccount(loginUser)
.flatMap(socialAccountIsValid -> {
if (socialAccountIsValid) {
return this.userService.getUserByEmail(loginUser.getEmail())
.flatMap(foundUser -> {
return updateUser(loginUser, foundUser);
})
.switchIfEmpty(Mono.defer(() -> insertUser(loginUser)))
.map(savedUser -> {
String jwts = jwt.createJwts(savedUser.get_id(), savedUser.getFirstName(), "user");
return new ResponseEntity<>(HttpStatus.OK);
});
} else {
return Mono.just(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
}
});
}
C'est parce que switchIfEmpty accepte Mono "par valeur". Cela signifie que même avant de vous abonner à votre mono, l'évaluation de ce mono alternatif est déjà déclenchée.
Imaginez une méthode comme celle-ci:
Mono<String> asyncAlternative() {
return Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
System.out.println("Hi there");
return "Alternative";
}));
}
Si vous définissez votre code comme ceci:
Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());
Cela déclenchera toujours une alternative, peu importe ce qui se passe pendant la construction du ruisseau. Pour résoudre ce problème, vous pouvez différer l'évaluation d'un deuxième mono en utilisant Mono.defer
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.defer(() -> asyncAlternative()));
De cette façon, il n'imprimera "Hi there" que si une alternative est demandée
PD:
J'élabore un peu sur ma réponse. Le problème auquel vous êtes confronté n'est pas lié à Reactor mais à Java langage lui-même et comment il résout les paramètres de la méthode. Examinons le code du premier exemple que j'ai fourni.
Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());
Nous pouvons réécrire ceci en:
Mono<String> firstMono = Mono.just("Some payload");
Mono<String> alternativeMono = asyncAlternative();
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
Ces deux extraits de code sont sémantiquement équivalents. Nous pouvons continuer à les déballer pour voir où se situe le problème:
Mono<String> firstMono = Mono.just("Some payload");
CompletableFuture<String> alternativePromise = CompletableFuture.supplyAsync(() -> {
System.out.println("Hi there");
return "Alternative";
}); // future computation already tiggered
Mono<String> alternativeMono = Mono.fromFuture(alternativePromise);
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
Comme vous pouvez le voir, le calcul futur a déjà été déclenché au moment où nous commençons à composer nos types Mono
. Pour éviter les calculs indésirables, nous pouvons envelopper notre avenir dans une évaluation différée:
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.defer(() -> asyncAlternative()));
Qui se déroulera dans
Mono<String> firstMono = Mono.just("Some payload");
Mono<String> alternativeMono = Mono.defer(() -> Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
System.out.println("Hi there");
return "Alternative";
}))); // future computation defered
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
Dans le deuxième exemple, l'avenir est pris au piège dans un fournisseur paresseux et son exécution n'est planifiée que lorsqu'elle sera demandée.