2

So I want to achieve the functionality of saving an user for that first i check whether user is present if present throw an exception else save the user But when I throw an exception from service layer .flatMap(user -> Mono.error(new IllegalArgumentException("User Exists with email " + user.getEmail())))

@Service
@RequiredArgsConstructor
public class AppUserService {

    private final AppUserRepository appUserRepository;

    public Flux<AppUser> getAllUsers() {
        return appUserRepository.findAll();
    }

    public Mono<AppUser> saveUser(AppUser appUser) {
        return getUser(appUser.getEmail())
                .flatMap(user -> Mono.error(new IllegalArgumentException("User Exists with email " + user.getEmail())))
                .switchIfEmpty(Mono.defer(() -> appUserRepository.save(appUser))).cast(AppUser.class).log();
    }

    public Mono<AppUser> getUser(String email) {
        return appUserRepository.findFirstByEmail(email);
    }
}

and in the controller layer if I handle it like .onErrorResume(error -> ServerResponse.badRequest().bodyValue(error))

@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class AppUserController {

    private final AppUserService appUserService;
    private final PasswordEncoder encoder;

    @GetMapping
    public Flux<AppUser> getAllUsers(@RequestHeader("email") String email) {
        return appUserService.getAllUsers();
    }

    @PostMapping
    @CrossOrigin
    public Mono<ResponseEntity<Object>> saveUser(@RequestBody Mono<AppUser> appUserMono) {
        return appUserMono
        .doOnSuccess(appUser -> appUser.setPassword(encoder.encode(appUser.getPassword())))
        .subscribeOn(Schedulers.parallel())
        .flatMap(appUserService::saveUser)
        .flatMap(savedAppUser -> ResponseEntity.created(URI.create("/user/" + savedAppUser.getId())).build())
        .onErrorResume(error -> Response entity.badRequest().bodyValue(error))
        .log();
    }

}

it throws an error on the console

Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.web.reactive.function.server.DefaultEntityResponseBuilder$DefaultEntityResponse and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

and returns 200 to the client

what am i doing wrong

After reading the error it seems its getting an empty value but if i debug the flow at the onErrorResume(error -> ..) the error variable has the error can't understand why it still throws the jackson error is it because jackson can't subscribe to ServerResponse or something around that

  • 1
    Why are you calling `cast`? The exception says something is most likely empty. You are somewhere returning something empty. Since you have not posted you full code, its impossible for us to know what function returns something empty. Are you sure the database calls dont return empty? Or the save? – Toerktumlare Sep 02 '21 at 19:13
  • Posted the entire code, Cast is there because the function returns Mono and `switchIfEmpty()` returns `Mono` in this case because of `flatMap()` called before it. I have checked the database its not returning empty its returning the user hence flow goes into `flatMap()` in service layer method and returns the `Error` to the controller and there the flow goes into the method `onErrorResume()` – Devbrat Dash Sep 04 '21 at 15:02
  • when something returns `Mono` its a sure sign, that what is earlier in the chain is doing something faulty. You should always avoid returning `object` as it is a code smell, the type system is there to help you, and you loose type information. The cast, is directly telling me that something you are doing is bad. – Toerktumlare Sep 04 '21 at 15:56
  • yeah I agree on that even i also had a feeling this must not be the correct way. what do you think how should the pipeline be designed if i want to achieve the following: when `saveUser()` is called : `getUser()` will return `Mono` so i need to **check whether user is present if present throw an exception and if it doesn't return any data or empty save the user?** – Devbrat Dash Sep 04 '21 at 16:14
  • i have done som experimenting, and i think you need to switch places with the `switchIfEmpty` and the `flatMap` so that it reacts to the empty part first, then you run the flatMap or not. – Toerktumlare Sep 04 '21 at 16:40
  • I can't right? because switchIfEmpty doesn't end the flow it returns a mono, so if getUser() returns no data switchIfEmpty will be called which will save the user and return Mono on which flatMap() will be called and as it will get the user it will throw an exception – Devbrat Dash Sep 05 '21 at 04:47

1 Answers1

2

There are few issues in your code.

Here's how you can get it to run (without the repository):

package test;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class AppUser {
  private String id;

  private String username;

  private String firstName;

  private String password;

  private String email;
}

@Service
@RequiredArgsConstructor
public class AppUserService {

  public Mono<AppUser> saveUser(AppUser appUser) {
    return getUser(appUser.getEmail())
        .flatMap(user -> Mono.<AppUser>error(
            new IllegalArgumentException("User Exists with email " + user.getEmail())))
        .switchIfEmpty(Mono.defer(() -> Mono.just(AppUser.builder().username("mustafa").build())));
  }

  public Mono<AppUser> getUser(String email) {
//    return Mono.defer(() -> Mono.just(AppUser.builder().email(email).build()));
    return Mono.defer(Mono::empty);
  }
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class AppUserController {

  private final AppUserService appUserService;

  @PostMapping
  @CrossOrigin
  public Mono<ResponseEntity<AppUser>> saveUser(@RequestBody Mono<AppUser> appUserMono,
                                                @RequestHeader("email") String email) {
    return appUserMono
        .subscribeOn(Schedulers.parallel())
        .flatMap(appUserService::saveUser)
        .flatMap(savedAppUser -> Mono.just(
            ResponseEntity.created(URI.create("/user/" + savedAppUser.getId())).body(savedAppUser)))
        .onErrorResume(error -> Mono.just(ResponseEntity.badRequest().build()))
        .log();
  }
}

Your controller's return type is Mono<ResponseEntity<AppUser>> but you are returning ServerResponse. I fixed this and removed the cast from your service code.

You can experiment with producing a 400 or a valid result by commenting/uncommenting the getUser body.

Maybe a clearer way to write the email check logic is with the good old if/else statement:

  public Mono<AppUser> saveUser(AppUser appUser) {
    if (emailExists(appUser.getEmail())) {
      return Mono.error(new IllegalArgumentException("User Exists with email "
          + appUser.getEmail()));
    } else {
      return Mono.just(AppUser.builder().username("mustafa").build());
    }
  }
Mustafa
  • 5,624
  • 3
  • 24
  • 40
  • This is working but i didn't get the part Mono.error( new IllegalArgumentException("User Exists with email " + user.getEmail()))), Is it going to create a wrapper or something to contain the IllegalArgumentException? – Devbrat Dash Sep 05 '21 at 16:01
  • The type parameter here just means that the subscriber to this `Mono` expects an object of type `AppUser`. `Mono#error` returns a `Mono` that contains an error. – Mustafa Sep 06 '21 at 10:24