Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get Flux or List from two Flux(Object) in Spring Reactive Programming?

I am new to Spring Reactive Project. In my Spring Boot Controller class I have

Flux<House> ( list of all houses in the database) and Flux<Image>

coming from service layer where given Flux<Image> are images for a given House like a list of houses each of which has its own collection of images. That means that I have to iterate over Flux<House> to get id of the home and then call findByHouseId on Image to get Flux<Image> , each Image has houseId property. Each image object has a property "cover" with string value "yes" or "no" and another property "type" having various string values like "bedroom". I want to collect a

list of Flux<Image> like List<Flux<Image>> or List<List<Image>>

containing only those Images for which "cover" == "yes" OR "type" == "bedroom". How to achieve this? Programming code will answer this question.

Below was attempted as part of the solution but even this partial solution does not work:

List<Flux<Image>> imagesList= new ArrayList<Flux<Image>>();
        Flux<House> houses = houseService.findAllHouses();

        houses.map(house ->  house.getId()).subscribe(id -> imageService.findByHouseId(id))
        .map(imageFlux ->imagesList.add(imageFlux));

Please note that I am looking for solution that does not involve invoking block() ( negating the benefit of being reactive) unless this is the only way.

In non-reactive situation this is what I want:

List<House> houses  --> List of all houses
Map<String,List<Image>> mapOfImages  --> where key is houseId 

so for each house I can easily get images for that house while iterating over houses in view template.

These (houses and mapOfImages) will be passed to model like

model.addAttribute("houses",houses);
model.addAttribute("mapOfImages",mapOfImages);

So now within thyemleaf view template I can:

<div data-th-each="house : ${houses}">
        <h3 data-th-text="${house.title}"></h3>
        <div data-th-each="image : ${mapOfImages.get(house.houseId)}">
          <h5 data-th-text="${image.fileName}"></h5>
          <h5 data-th-text="${image.type}"></h5>
        </div>
 </div>   

The database being used is Mongodb which has two independent collections: house and image i.e. there are no embedded collections and the only thing that tie up an image to a house is the houseId property on image.

like image 637
ace Avatar asked Apr 05 '18 15:04

ace


People also ask

How do I get a list from Flux?

Ways to convert Flux into CollectioncollectList() : accumulate sequence into a Mono<List> . collectSortedList() : accumulate sequence and sort into a Mono<List> . collectMap() : convert sequence into a Mono<Map> .

What is the difference between mono and flux?

Mono is more relatable to the Optional class in Java since it contains 0 or 1 value, and Flux is more relatable to List since it can have N number of values.

What is spring reactive WebFlux?

Spring WebFlux is the alternative to Spring MVC module. Spring WebFlux is used to create fully asynchronous and non-blocking application built on event-loop execution model. Below diagram from Spring Official Documentation provides great insight on comparison of Spring WebFlux to Spring Web MVC.

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.


1 Answers

You basically want to transform a stream of House objects into a stream of Image objects.

So, use flatMap() which converts Flux<Flux<T>> (or Flux<Mono<T>>) into Flux<T>.

Mono<List<Image>> images = houseService.findAllHouses() // returns Flux<House>
  .flatMap(house -> imageService.findByHouseId(id)      // returns Flux<Image>
                        .filter(image -> "yes".equals(image.getCover()) && ... )) 
  .collectList();                                       //returns Mono<List<Image>>

Its OK to use collectList() here, because you most possibly want to convert images into a single Mono<ServerResponse> object anyway:

return images.map(imageList -> ServerResponse.ok()
                                       .body(fromObject(imageList)));

Also, I recommend reading Project Reactor reference about when to use which operator.


EDIT:

So, you want a Mono<Map<String, Collection<Image>> instead. My answer would depend whether the Image class has a reference back to the House. It's still OK if it doesn't, the answer would be more verbose though.

I'll describe the hard way, where Image doesn't contain the House id. We need sth., which links the list of images belonging to a specific house. You can define a class for this purpose or just use reactor.util.function.Tuple2. Having that, we can just use collectMap():

Mono<Map<String, Collection<Image>>> images = houseService.findAllHouses()
     .flatMap(house -> imageService.findByHouseId(id)
                           .filter(...)
                           .map(image -> Tuples.of(id, image)))
     .collectMultiMap(tuple -> tuple.getT1(), 
                      tuple -> tuple.getT2());

Of course, if you have a reference back to the House, then you could omit the map() again and use collectMultiMap directly:

...
    .collectMultiMap(image -> image.getHouseId(), image -> image);
like image 72
MuratOzkan Avatar answered Sep 21 '22 01:09

MuratOzkan