html_programador
Tengo dos métodos.
método principal:
@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));
}
});
}
Y este método invocado (el servicio llama a una API externa):
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);
}
});
}
En el ejemplo anterior, switchIfEmpty()
siempre se llama desde el método principal, incluso cuando un resultado con Mono.empty()
es regresado.
No puedo encontrar una solución para este problema simple.
Lo siguiente tampoco funciona:
Mono.just(null)
Debido a que el método arrojará un NullPointerException
.
Lo que tampoco puedo usar es el método flatMap para comprobar que foundUser
es nulo.
Lamentablemente, flatMap no recibe ninguna llamada en caso de que regrese Mono.empty()
por lo que tampoco puedo agregar una condición aquí.
@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));
}
});
}
Alex
Es porque switchIfEmpty acepta Mono “por valor”. Lo que significa que incluso antes de suscribirse a su mono, la evaluación de este mono alternativo ya está activada.
Imagina un método como este:
Mono<String> asyncAlternative() {
return Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
System.out.println("Hi there");
return "Alternative";
}));
}
Si defines tu código así:
Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());
Siempre activará una alternativa sin importar qué durante la construcción de la transmisión. Para abordar esto, puede aplazar la evaluación de un segundo mono usando Mono.defer
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.defer(() -> asyncAlternative()));
De esta manera, solo imprimirá “Hola” cuando se solicite una alternativa
UPD:
Elaborando un poco mi respuesta. El problema al que se enfrenta no está relacionado con Reactor sino con el propio lenguaje Java y cómo resuelve los parámetros del método. Examinemos el código del primer ejemplo que proporcioné.
Mono<String> result = Mono.just("Some payload").switchIfEmpty(asyncAlternative());
Podemos reescribir esto en:
Mono<String> firstMono = Mono.just("Some payload");
Mono<String> alternativeMono = asyncAlternative();
Mono<String> result = firstMono.switchIfEmpty(alternativeMono);
Estos dos fragmentos de código son semánticamente equivalentes. Podemos seguir desenvolviéndolos para ver dónde está el problema:
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);
Como puede ver, el cálculo futuro ya se activó en el momento en que comenzamos a componer nuestro Mono
tipos Para evitar cálculos no deseados, podemos envolver nuestro futuro en una evaluación diferida:
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.defer(() -> asyncAlternative()));
que se desenvolverá en
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);
En el segundo ejemplo, el futuro está atrapado en un proveedor perezoso y está programado para ejecutarse solo cuando se solicite.
UPD: 2022:
Desde hace algún tiempo, el proyecto reactor viene con una API alternativa para envolver futuros calculados con entusiasmo, lo que da como resultado lo mismo: atrapar el cálculo ansioso en un proveedor perezoso:
Mono<String> result = Mono.just("Some payload")
.switchIfEmpty(Mono.fromCompletionStage(() -> alternativePromise()));
-
Intenté esto, pero el método como lo defines
asyncAlternative()
siempre se activa a pesar de laMono.defer()
.– html_programador
30 de enero de 2019 a las 19:25
-
@Trace, ¿puedes mostrarme lo que has intentado? Porque el único propósito de defer es no permitir que la evaluación ocurra antes de tiempo.
– Alex
30 de enero de 2019 a las 19:34
-
Por favor, vea la publicación actualizada. En modo de depuración he visto que el
Mono.defer
se llama después, pero eso no quita que siempre se ejecuta, incluso cuandothis.userService.getUserByEmail(loginUser.getEmail())
no regresaMono.empty()
.– html_programador
30 de enero de 2019 a las 19:44
-
It'll always trigger alternative no matter what during stream construction.
Entonces cuál es el uso. Es un método llamadoswitchIfEmpty
. Algunas cosas realmente no tienen sentido.– html_programador
30 de enero de 2019 a las 19:54
-
Tu respuesta fue correcta. Lo rompí, resultó que la razón por la cual
switchIfEmpty
se activó fue porque de hechoupdateUser
devolvió un cuerpo vacío con código de estado http204
! Modifiqué la API un poco a regañadientes, pero ahora funciona correctamente. ¡Gracias por esto!– html_programador
1 de febrero de 2019 a las 0:35
Para aquellos que, a pesar de la respuesta bien votada, todavía no entienden por qué tal comportamiento:
Las fuentes del reactor (Mono.xxx y Flux.xxx) son:
-
Perezosamente evaluado : el contenido de la fuente se evalúa/activa solo cuando un suscriptor se suscribe a él;
-
o evaluado ansiosamente : el contenido de la fuente se evalúa inmediatamente incluso antes de que el suscriptor se suscriba.
Expresiones como Mono.just(xxx)
, Flux.just(xxx)
, Flux.fromIterable(x,y,z)
están ansiosos
Mediante el uso defer()
, obligas a que la fuente se evalúe de forma perezosa. Es por eso que la respuesta aceptada funciona.
Así que haciendo esto:
someMethodReturningAMono()
.switchIfEmpty(buildError());
con buildError()
confiando en una fuente ansiosa para crear un Mono alternativo será siempre evaluarse antes de la suscripción:
Mono<String> buildError(){
return Mono.just("An error occured!"); //<-- evaluated as soon as read
}
Para evitar eso, haz esto:
someMethodReturningAMono()
.switchIfEmpty(Mono.defer(() -> buildError()));
Lea esta respuesta para más.
-
simplemente lo explicó. muchas gracias.
– Arundev
7 sep 2022 a las 8:43
No estoy seguro si entendí bien esta oración.
switchIfEmpty() is always called from the main method, even when a result with Mono.empty() is returned.
. Se supone que se llama no?– Barat
26 de enero de 2019 a las 5:39
¿Puedes explicar un poco más tu problema? ”switchIfEmpty() siempre se llama desde el método principal, incluso cuando se devuelve un resultado con Mono.empty()”. Este es el comportamiento esperado.
– Prashant Pandey
26 de enero de 2019 a las 8:21
@Barath Lo que quiero lograr es que si el servicio externo devuelve 404, puedo devolver un Mono con valor
null
de la capa de servicio que puede ser manejada por el método principal. Supongo que también podría arrojar un error, pero prefiero no hacerlo. El 404 debe manejarse en la capa de servicio, y cuando no se encuentra un usuario, esta es la lógica de la aplicación con la que creo que debería manejarif
, y no por el manejo de excepciones. voy a revisarswitfhIfEmpty
en los documentos. Aún así, ¿una sugerencia de trabajo?– html_programador
26 de enero de 2019 a las 15:06
@PrashantPandey Consulte el comentario anterior.
– html_programador
26 de enero de 2019 a las 15:06
@Trace, su código aún funciona, si es 404, está regresando
Mono.empty()
que va a llamarswitchIfEmpty
. De todos modos, si desea manejar errores si eso es lo que está buscando, puede usaronErrorResume()
y manejar apropiadamente o también puede usaronErrorReturn()
. guía– Barat
26 de enero de 2019 a las 16:23