I am new to RXjava functional programming. I am writing a post endpoint with multiple conditions:
I am facing a challenge to write these if else conditions in functional programming. tried with switchifEmpty and not able to write the code in that.
Here is a sample code.
public Mono<Product> createProduct(final Tuple2<String, productdto> tuple2) {
final Product productdto = tuple2.getT2();
return Mono.just(tuple2.getT1())
.map(cartRepository::findById)
.defaultIfEmpty(cartRepository.save(
cart.builder()
.id(tuple2.getT1())
.build()))
.flatMap(cartres -> cartres)
.flatMap(cartres -> {
final Product product = Product.builder()
.id(1234)
.productId(productDTO.getProductId())
.productName(productDTO.getProductName())
.build();
return productRepository.save(product)
.map(saveCart -> cart.builder()
.id(cartres.getId()).build())
.flatMap(cartRepository::save);
});
}).then(Mono.just(productDto));
}
I know this isn't the best way of handling complex logic, but it's how I've been doing it for basically all my time when developing using Spring WebFlux.
The problem with if-else logic is that you can't exit out of the reactive chain. If you return something in a map call it just gets thrown into the next mapper. The only solution I see to solve this problem is using exceptions to escape the reactive chain.
That's why I extended the Exception class and created CustomException:
public class CustomException extends Exception {
public CustomException(String s) {
super(s);
}
}
We can now use our CustomException to implement complex if-else logic by just throwing errors and catching them at the end of the reactive chain.
For this example I'm just gonna use a simple login endpoint to demonstrate how to use it.
Let's first create a LoginForm record to receive the request body.
public record LoginForm(String username, String password) {}
Now let's implement a simple login endpoint with a AccountRepository and then creating a session using the 'SessionRepository' (Both of these repositories aren't implemented, just imagine they exist).
@PostMapping("/login")
public Mono<String> logIn(@RequestBody LoginForm loginForm) {
// Get the account using the username
return accountRepository.getAccountByUsername(loginForm.username()).flatMap(account -> {
// Check password
if(!account.getPassword().equals(loginForm.password()) {
// Return "Login failed!" using the CustomException we created
return Mono.error(new CustomException("Login failed!"));
}
// Create session
return sessionRepository.save(new Session(account.getId(), randomToken());
}).map(session -> "Login successful! " + session.getToken())
.onErrorResume(CustomException.class, e -> Mono.just(e.getMessage()))
.onErrorReturn("Server error!");
}
I hope that those who looked at this answer could understand how I implemented some if-else logic in this example. If you have any additions please comment below it.
I understand that there is a complex if-else-logic that you have to implement with a reactive chain with Spring WebFlux invocations of .map(), .flatMap() or defaultIfEmpty().
These reactive operators can't always replace complex if-else-logic.
Implementing if-else-decisions must be done inside your .map() or .flatMap() handlers.
In your case, you could implement a java.util.Function<> or a simple method for each step 1..8
A first example with a function that provides you with a cart by loading it from the repo or by creating a new one can be seen below.
Furthermore, it shows how you can use the zipWith() operator to add more data to the reactive chain.
Here, the existence of the product in the database is checked and the result is passed as a boolean downstream.
Based on this boolean, you can use an if-else-statement to either return the existing product or to create a new one.
public Mono<Product> createProduct(final Tuple2<String, Product> tuple2) {
final Product productDTO = tuple2.getT2();
return Mono.just(tuple2.getT1())
// .map(cartRepository::findById)
// .defaultIfEmpty(cartRepository.save(
// cart.builder()
// .id(tuple2.getT1())
// .build()))
.flatMap(provideCart(tuple2)) // see implemented method further below
.zipWith(productRepository.existsById(productDTO.getId()))
.flatMap(maybeCreateProduct(productDTO)); // see implemented method further below
}
private Function<String, Mono<Cart>> provideCart(final Tuple2<String, Product> tuple2) {
return id -> cartRepository.findById(id)
.switchIfEmpty(cartRepository.save(
Cart.builder()
.id(tuple2.getT1())
.build()));
}
private Function<Tuple2<Cart, Boolean>, Mono<Product>> maybeCreateProduct(Product productDTO) {
return cartresAndProductExists -> {
Cart cart = cartresAndProductExists.getT1();
Boolean productExists = cartresAndProductExists.getT2();
if (productExists) {
return productRepository.findById(productDTO.getProductId())
} else {
return productRepository.save(Product.builder()
.id("1234")
.productId(productDTO.getProductId())
.productName(productDTO.getProductName())
.build());
}
};
}
One has to be careful to keep the code readable and to avoid an endless chain of reactive steps.
Extract complex steps into separate methods and maybe even rethink the process in the first place to come to a more simple and streamlined solution
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With