Using project reactor mergeWith() operator in order to achieve “if/elseif/else” branching logic

删除回忆录丶 提交于 2019-12-11 06:05:01

问题


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 3 Mono objects and merges them into a Flux;
  • next() returns the first available result as a Mono;
  • single() will ensure that the Mono 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 whatever Mono 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!