Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to perform an action only if the Mono is empty and throw an error if not empty

I'm trying to convert a project to use Spring WebFlux and am running into a problem getting some basic business logic working. I have a repository layer that is responsible for retrieving / persisting records and a service layer that is responsible for the business rules of the application. What I want to do (in the service) layer is check if a user already exists for the given username. If so, I want to respond with an error. If not, I want to allow the insert to happen.

I call a method on the repository layer that will find a user by username and if not found it will return an empty Mono. This is working as expected; however, I have tried various combinations of flatMap and (defaultIfEmpty and swithIfEmpty) but am unable to get it to compile / build.

    public Mono<User> insertUser(User user) {
        return userRepository.findByUsername(user.username())
            .flatMap(__ -> Mono.error(new DuplicateResourceException("User already exists with username [" + user.username() + "]")))
            .switchIfEmpty(userRepository.insertUser(user));
    }

The error that I'm getting is that Mono<Object> cannot be converted to Mono<User>, so the swithIfEmpty doesn't seem to reflect the appropriate type and casting doesn't seem to work either.

like image 504
cgaskill Avatar asked Sep 27 '19 02:09

cgaskill


People also ask

Does Mono empty return null?

A Mono can never complete with a value of null so you don need to test in the map. Your userRepository should return an empty Mono if the user is missing.

What is mono empty ()?

Mono. empty() is a method invocation that returns a Mono that that completes without emitting any item. – Sotirios Delimanolis. May 15, 2020 at 15:34.

How do you extract data from a mono object?

Extract data from Mono in Java – blocking way This way of extracting data is discouraged since we should always use the Reactive Streams in an async and non-blocking way. We can use the block() method that subscribes to a Mono and block indefinitely until the next signal is received. Output: Data from Mono: Hello!

What does mono void mean?

The reason that Mono<Void> is a special case is that, as you point out, Void is a class that can never be instantiated by design. There's therefore no such thing as an instance of Void , which means the Mono can never emit a value before its completion signal.


2 Answers

After additional testing, and taking into consideration the responses from my fellow developers, I have landed on the following solution:

    public Mono<User> insertUser(User user) {
        return userRepository.findByUsername(user.username())
            .flatMap(__ -> Mono.error(new DuplicateResourceException("User already exists with username [" + user.username() + "]")))
            .switchIfEmpty(Mono.defer(() -> userRepository.insertUser(user)))
            .cast(User.class);
    }

As Thomas stated, the compiler was getting confused. My assumption is because the flatMap was returning a Mono with an error and the switchIfEmpty was returning a Mono with a User so it reverts to a Mono with an Object (hence the additional .cast operator to get it to compile).

The other addition was to add the Mono.defer in the switchMap. Otherwise, the switchIfEmpty was always firing.

I'm still open to other suggestions / alternatives (since this seems like it would be a fairly common need / pattern).

like image 147
cgaskill Avatar answered Nov 02 '22 18:11

cgaskill


Why you are getting this compiler error is because of the following.

flatmap takes what is in your completed Mono and tries to convert it to whatever type it can infer. Mono.error contains a type and this type is of Object.

One way could instead be to move your logic into the flatmap.

// This is just example code using strings instead of repos
public Mono<String> insertUser(String user) {
    return Mono.just(user)
            // Here we map/convert instead based on logic
            .flatMap(__ -> {
                if (__.isEmpty())
                    return Mono.error(new IllegalArgumentException("User already exists with username [" + user + "]"));
                return Mono.just(user);
            }).switchIfEmpty(Mono.just(user));
}

switchIfEmpty is not super good for making logical decisions imho. The documentation states

Fallback to an alternative Mono if this mono is completed without data

It's more of a fallback to something else if we don't get anything, so we can keep the flow of data going.

You can also

Mono.empty().doOnNext(o -> {
        throw new IllegalArgumentException("User already exists with username [" + o + "]");
    }).switchIfEmpty(Mono.just("hello")).subscribe(System.out::println);
like image 3
Toerktumlare Avatar answered Nov 02 '22 16:11

Toerktumlare