简体   繁体   中英

How to make async call inside another async method in Webflux?

Explanation of the question is bit a long. Kindly take a minute and help!

I have two http calls which will give the following data.

1st http request call will return <Mono<List<Chips>>

[
  {
    "id": 1,
    "name": "redlays"
  },
  {
    "id": 2,
    "name": "yellowlays"
  },
  {
    "id": 3,
    "name": "kurkure"
  }
]

Chips Model is

@Data
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Chips {
    private int id;
    private String name;
}

2nd http request call will return Mono<ChipsDetails> based on Id

{
    "id": 1,
    "color": "red",
    "grams": "50"
}

ChipsDetails Model as below,

@Data
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ChipsDetails {
    private int id;
    private String color;
    private String grams;
}

I have done the Implementation using Webflux. Here I have used three models which are Chips , ChipsDetails and ChipsFullDetails .

Model Chips will have two attributes id and name then Model ChipsDetails will have three attributes id , color and grams whereas Model ChipsFullDetails will have combination of Chips and ChipsDetails attributes which are id , name , color and grams

@RestController
@RequestMapping("/chips")
public class ChipsController {

    @Autowired
    ChipsService chipsService;

    @GetMapping
    public Mono<List<ChipsFullDetails>> getAllChips() {
        return chipsService.getChips()
                .map(f -> {
                            List<ChipsFullDetails> chipsFullDetails = new ArrayList<>();
                            f.forEach(a -> {
                                ChipsFullDetails chipsFullDetail = new ChipsFullDetails();
                                chipsFullDetail.setId(a.getId());
                                chipsFullDetail.setName(a.getName());

                                chipsService.getChipsDetails(a.getId())
                                        .subscribe(b -> {
                                            chipsFullDetail.setColor(b.getColor());
                                            chipsFullDetail.setGrams(b.getGrams());
                                        });
                                chipsFullDetails.add(chipsFullDetail);

                            });
                            return chipsFullDetails;
                        }
                );
    }
}

Here chipsService.getChips() will return Mono<List<Chips>> This is the 1st call and chipsService.getChipsDetails(a.getId()) will return Mono<ChipsDetails> This is the 2nd http request call.

The result of the implementation will be ChipsFullDetails

@Data
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ChipsFullDetails {
    private int id;
    private String name;
    private String color;
    private String grams;
}

The problem is ChipsFullDetails returns null for color and grams attributes which we are getting from the 2nd http call even though it is subscribed inside.

How to achieve when Second Http call ie chipsService.getChipsDetails(a.getId()) depending on the result of 1st http call ( chipsService.getChips() ) in asynchronous way?

Is this possible to achieve without blocking both the calls?

I'd transform the initial Mono<List<Chips>> into a Flux<Chips> first, so that you can flatMap on each element, eg something along those lines:

public Mono<List<ChipsFullDetails>> getAllChips() {
    return chipsService
            .getChips()
            // Mono<List> to Flux:
            .flatMapIterable(Function.identity())
            // flat map on each element:
            .flatMap(this::buildChipsFullDetails)
            // Flux back to Mono<List>:
            .collectList();
}

private Mono<ChipsFullDetails> buildChipsFullDetails(Chips chips) {
    return chipsService
            .getChipsDetails(chips.getId())
            // once you get both chips and details, aggregate:
            .map(details -> buildChipsFullDetails(chips, details));
}

private ChipsFullDetails buildChipsFullDetails(Chips chips, ChipsDetails details) {
    // straightforward synchronous code:
    ChipsFullDetails chipsFullDetail = new ChipsFullDetails();
    chipsFullDetail.setId(chips.getId());
    chipsFullDetail.setName(chips.getName());
    chipsFullDetail.setColor(details.getColor());
    chipsFullDetail.setGrams(details.getGrams());
    return chipsFullDetail;
}

I basically disagree with the idea of working with a Flux , though I admit I have it as well.

I would say that if you want to get details for a list of chips then you should make an endpoint that does that. Then it will be a single call.

For you original question, there is a way to do it without going to Flux , but it reads a bit funny:

ParameterizedTypeReference<List<Chip>> chipList = new ParameterizedTypeReference<List<Chip>>() {};

public Mono<List<ChipDetails>> getChipDetails() {
    return webClient.get().uri("chips").retrieve().bodyToMono(chipList).flatMap(chips -> {
        return Mono.zip(chips.stream().map(chip -> webClient.get().uri("chipDetails?id="+chip.getId()).retrieve().bodyToMono(ChipDetails.class)).collect(Collectors.toList()), details -> {
            List<ChipDetails> chipDetails = new ArrayList<>();
            for (Object o : details) {
                chipDetails.add((ChipDetails) o);
            }
            return chipDetails;
        });
    });
}

This uses Mono.zip to create a sort of batch request out of each of the Chip entries in the list executes them all at once. Flux will probably end up doing more or less the same thing but not really.

If you just make the endpoint you need, then:

ParameterizedTypeReference<List<ChipDetails>> detailsList = new ParameterizedTypeReference<List<ChipDetails>>() {};

public Mono<List<ChipDetails>> getChipDetailsReal() {
    return webClient.post().uri("chipDetails").body(webClient.get().uri("chips").retrieve().bodyToMono(chipList), chipList).retrieve().bodyToMono(detailsList);
}

This approach avoids repeated calls to the same endpoint and is doing what you want.

I'm not a fan of using Flux when you really mean List . A Flux is a streaming thing with backpressure and sophisticated capabilities whereas a List is just a List.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM