Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Mono and Flux in handler functions with Spring WebFlux Reactive ways

Check for -->

My model looks something like this.

@Document
public class PlanDetails {

    @Id
    private String id;
    private String name;
    private Double balance;
    private Double internet;
    private Date date;

    --> //String of id's basically.
    private List<String> members;

    public PlanDetails(){}

    public PlanDetails(String id, String name, Double balance, Double internet, Date date, List<String> members){
        this.id = id;
        this.name = name;
        this.balance = balance;
        this.internet = internet;
        this.date = date;
        this.members = members;
    }

    public String getId() {
        return id;
    }

    /* Getter setters ommited for brevity */

Here is my handler class to handle my functional endpoints.

package com.startelco.plandetailsapi.handler;

import com.startelco.plandetailsapi.model.PlanDetails;
import com.startelco.plandetailsapi.repository.PlanRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import static org.springframework.http.MediaType.APPLICATION_JSON;
@Component
public class PlanDetailsHandler {

    private PlanRepository repository;


    public PlanDetailsHandler(PlanRepository repository){
        this.repository = repository;
    }

    //Get All Users
    public Mono<ServerResponse> getAllUsers(ServerRequest request){
        Flux<PlanDetails> users = repository.findAll();

        return ServerResponse.ok()
                .contentType(APPLICATION_JSON)
                .body(users,PlanDetails.class);
    }

    --> //Get User by ID
    public Mono<ServerResponse> getUserDetails(ServerRequest request){
        String id = request.pathVariable("id");

        Mono<PlanDetails> userMono = repository.findById(id);
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();

        return userMono.flatMap(user ->
                ServerResponse.ok()
                        .contentType(APPLICATION_JSON)
                        .body(BodyInserters.fromObject(user))
                        .switchIfEmpty(notFound)
                );
    }


    //Create a user
    public Mono<ServerResponse> saveUser(ServerRequest request){
        Mono<PlanDetails> userMono = request.bodyToMono(PlanDetails.class);

        return userMono.flatMap(user ->
                ServerResponse.status(HttpStatus.CREATED)
                        .contentType(APPLICATION_JSON)
                        .body(repository.save(user),PlanDetails.class)
                );
    }


    //Update user by ID
    public Mono<ServerResponse> updateUser(ServerRequest request) {
        String id = request.pathVariable("id");
        Mono<PlanDetails> existingUserMono = this.repository.findById(id);
        Mono<PlanDetails> userMono = request.bodyToMono(PlanDetails.class);

        Mono<ServerResponse> notFound = ServerResponse.notFound().build();

        return userMono.zipWith(existingUserMono,
                (user, existingUser) ->
                        new PlanDetails(existingUser.getId(), user.getName(), user.getBalance(), user.getInternet(),user.getDate(),user.getMembers())
        )
                .flatMap(user ->
                        ServerResponse.ok()
                                .contentType(APPLICATION_JSON)
                                .body(repository.save(user), PlanDetails.class)
                ).switchIfEmpty(notFound);
    }



    //Delete All Users
    public Mono<ServerResponse> deleteAllUsers(ServerRequest request) {
        return ServerResponse.ok()
                .build(repository.deleteAll());
    }
}

QUESTION: What is the Reactive way of coding getMemberDetails with reusing the previously written function getUserDetails.?

Requirement.

DESIRED FUNCTIONALITY : Get Member Details Function.

public Mono<ServerResponse> getMemberDetails(ServerRequest request) {
   --> //Pseudocode
   String id = request.pathVariable("id");

   Mono<PlanDetails> userMono = repository.findById(id);
   Mono<ServerResponse> notFound = ServerResponse.notFound().build();

   if(userMono.member is not null){
       int length = userMono.member.length
       Flux<PlanDetails> memberFlux;
       for(int i=0;i<length;i++){
            Mono<PlanDetails> member = getUserDetails(id=userMono.member[i]);
            memberFlux.add(member);
       }
    }else{
       return ServerResponse.ok().build(memberFlux=null or empty array);
    }

    return ServerResponse.ok().build(memberFlux.flatmap);

}

DESIRED BEHAVIOR

{
    "id": "5b42ecc11cde674475cab39a",
    "name": "Alex Svirsky",
    "balance": 140,
    "internet": 20,
    "date": 1531107095659,
    "members": [
        "5b42ecdd1cde674475cab39b",
        "5b42ed421cde674475cab39c",
        "5b42ed5d1cde674475cab39d"
    ]
}

REST call in my routes is defined as http://localhost:8080/users/members/5b42ecc11cde674475cab39a

[    {
        "id": "5b42ecdd1cde674475cab39b",
        "name": "Bob Marley",
        "balance": 120,
        "internet": 9,
        "date": 1531107095559,
        "members": null
    },
    {
        "id": "5b42ed421cde674475cab39c",
        "name": "Charlie Sheen",
        "balance": 10,
        "internet": 9,
        "date": 1531107095555,
        "members": null
    },
    {
        "id": "5b42ed5d1cde674475cab39d",
        "name": "Dale Carnegie",
        "balance": 100,
        "internet": 9,
        "date": 1531107055555,
        "members": null
    }
]
like image 551
technazi Avatar asked Jul 09 '18 10:07

technazi


People also ask

What is mono and flux in WebFlux?

Project Reactor is the implementation of Reactive Streams specification. Reactor provides two types: Mono: implements Publisher and returns 0 or 1 elements. Flux: implements Publisher and returns N elements.

What is mono and flux in reactive programming?

Mono and Flux are both implementations of the Publisher interface. In simple terms, we can say that when we're doing something like a computation or making a request to a database or an external service, and expecting a maximum of one result, then we should use Mono.

What is flux in reactive programming?

A Flux object represents a reactive sequence of 0.. N items, while a Mono object represents a single-value-or-empty (0..1) result.

How does Spring WebFlux work internally?

Spring WebFlux internally uses Project Reactor and its publisher implementations, Flux and Mono. The new framework supports two programming models: Annotation-based reactive components. Functional routing and handling.


1 Answers

In my opinion there isn't much that can be reused in that first method: as the two methods both return a fully-formed ServerResponse, you can't compose the originals. Which is merely repository.findById() with some boilerplate to transform that into an ok response or a 404 response...

So what you actually need is a way to compose getMemberDetails on top of many calls to repository.findById. If I interpret your pseudocode correctly, you want the response to just be a Flux<PlanDetail> of all the "members" under an original "user" (disregarding information about said original user)?

You should be able to do that reactively with a flatMap:

public Mono<ServerResponse> getMemberDetails(ServerRequest request) {
  String id = request.pathVariable("id");

   Mono<PlanDetails> userMono = repository.findById(id);
   Mono<ServerResponse> notFound = ServerResponse.notFound().build();

   return userMono
       //transform from user mono to Flux of member details
       .flatMap(user -> Flux.fromArray(user.member)) //if `member` is an Iterable use the following instead:
       //.flatMapIterable(user -> user.member)
       //now we have a Flux of member IDs, go get details
       .flatMap(repository::findById)
       //this will naturally ignore not found members
       //if no member is found or array of IDs is empty, the main sequence is itself empty at this point
       .switchIfEmpty(notFound);
}

Only caveat of this approach is that it doesn't distinguish between "not found" (the original user is not found) vs "no content" (the user has no members that can be found).

like image 131
Simon Baslé Avatar answered Sep 19 '22 03:09

Simon Baslé