问题
I am trying to use project reactor mergeWith
operator in order to achieve a if/elseif/else
branching logic as described here: RxJS, where is the If-Else Operator.
The provided samples are written in RxJS but the underlying idea remains the same.
Basically the idea is to use the filter
operator on 3 monos/publishers
(therefore with 3 different predicates) and merge the 3 monos
as follows (here they are RxJS Observables
of course):
const somethings$ = source$
.filter(isSomething)
.do(something);
const betterThings$ = source$
.filter(isBetterThings)
.do(betterThings);
const defaultThings$ = source$
.filter((val) => !isSomething(val) && !isBetterThings(val))
.do(defaultThing);
// merge them together
const onlyTheRightThings$ = somethings$
.merge(
betterThings$,
defaultThings$,
)
.do(correctThings);
I have copied and pasted the relevant sample from the above article.
Consider that something$
, betterThings$
and defaultThings$
are our monos isSomething
& isBetterThings
are the predicates.
Now here are my 3 real monos/publishers
(written in java):
private Mono<ServerResponse> validateUser(User user) {
return Mono.just(new BeanPropertyBindingResult(user, User.class.getName()))
.doOnNext(err -> userValidator.validate(user, err))
.filter(AbstractBindingResult::hasErrors)
.flatMap(err ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(err.getAllErrors()))
);
}
private Mono<ServerResponse> validateEmailNotExists(User user) {
return userRepository.findByEmail(user.getEmail())
.flatMap(existingUser ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject("User already exists."))
);
}
private Mono<ServerResponse> saveUser(User user) {
return userRepository.save(user)
.flatMap(newUser -> status(CREATED)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(newUser))
);
}
Here is the top level method that needs to merge the three publishers
:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest.bodyToMono(User.class)
.mergeWith(...)
}
I am not sure how to use the mergeWith()
operator... I have tried the Mono.when()
static operator which takes several publishers (good for me) but returns a Mono<void>
(bad for me).
Can anyone please help?
P.S. I am sure you will excuse the mix between RxJS (js) and Reactor code (java). I meant to use my knowledge from RxJS in order to achieve a similar goal in my Reactor app. :-)
edit 1: I have tried this:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.flatMap(user -> validateUser(user).or(validateEmailNotExists(user)).or(saveUser(user))).single();
}
But I get this error: NoSuchElementException: Source was empty
edit 2: Same with (notice the parenthesis):
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.flatMap(user -> validateUser(user).or(validateEmailNotExists(user)).or(saveUser(user)).single());
}
edit 3: Same error with a Mono<User>
:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
Mono<User> userMono = serverRequest.bodyToMono(User.class);
return validateUser(userMono)
.or(validateEmailNotExists(userMono))
.or(saveUser(userMono))
.single();
}
edit 4: I can confirm that at least one of the three monos will always emit. It is when I use the or()
operator that something goes wrong...
If I use this, all my tests pass:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest.bodyToMono(User.class)
.flatMap(user -> Flux.concat(validateUser(user), validateEmailNotExists(user), saveUser(user)).next().single());
}
I have used the concat()
operator here to preserve the order of operations.
Do you know what I am getting wrong with the or()
operator?
edit 5: I have tried with the cache()
operator as follows to no avail:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.cache()
.flatMap(user -> validateUser(user)
.or(validateEmailNotExists(user))
.or(saveUser(user))
.single()
);
}
回答1:
Your current code sample implies that your 3 methods returning Mono<ServerResponse>
should be taking a Mono<User>
rather than a User
, so you may need to alter something there.
However, I digress - that doesn't seem to be the main question here.
From what I understand of the pattern described in that link, you're creating 3 separate Mono
objects, only one of which will ever return a result - and you need a Mono
of whichever one of your original 3 Mono
objects returns.
In that case, I'd recommend something like the following:
Mono<ServerResult> result = Flux.merge(validateUser(user), validateEmailNotExists(user), saveUser(user)).next().single();
Breaking it down:
- The static
Flux.merge()
method takes your 3Mono
objects and merges them into aFlux
; next()
returns the first available result as aMono
;single()
will ensure that theMono
emits a value, as oppose to nothing at all, and throw an exception otherwise. (Optional, but just a bit of a safety net.)
You could also just chain Mono.or()
like so:
Mono<ServerResult> result = validateUser(user).or(validateEmailNotExists(user)).or(saveUser(user)).single();
The advantages to this approach are:
- It's arguably more readable in some cases;
- If it's possible that you'll have more than one
Mono
in your chain return a result, this allows you to set an order of precedence for which one is chosen (as oppose to the above example where you'll just get whateverMono
emitted a value first.)
The disadvantage is potentially one of performance. If saveUser()
returns a value first in the above code, then you still have to wait for the other two Mono
objects to complete before your combined Mono
will complete.
来源:https://stackoverflow.com/questions/57656690/using-project-reactor-mergewith-operator-in-order-to-achieve-if-elseif-else