Search code examples
javaspring-webfluxproject-reactorreactiveflux

Can I have Flux as a field of ServerResponse body?


I'm new to Spring Reactive Web, and i met the following issue. I want to create a microservice A with an endpoint which accepts a number N, sends N requests to microservice B (which returns a string for each request), wraps the strings into objects, combines them into a List/Flux (?) and returns a JSON with those objects, like:

{
  "number": 4,
  "objects": [
    {
       "name": "first"
    },
    {
       "name": "second"
    },
    {
       "name": "third"
    },
    {
       "name": "fourth"
    }
  ]
}

I want to use a functional endpoint for this. So i tried the following (made my best to simplify it):

public class MyObject {
    private String name; // here should be a value received from B
    // ...
}
public class MyResponse {
    private int number;
    private Flux<MyObject> objects; // or List?
    // ...
}
@Component
@RequiredArgsConstructor
public class MyHandler {

    private final MyClient client;

    public Mono<ServerResponse> generate(ServerRequest serverRequest) {
        return serverRequest.bodyToMono(MyRequestBody.class)
                .flatMap(request -> buildServerResponse(HttpStatus.OK, buildResponseBody(request)));
    }

    private Mono<ServerResponse> buildServerResponse(HttpStatus status, Mono<MyResponse> responseBody) {
        return ServerResponse.status(status)
                .contentType(MediaType.APPLICATION_JSON)
                .body(responseBody, MyResponse.class);
    }

    private Mono<MyResponse> buildResponseBody(MyRequestBody request) {
        return Mono.just(MyResponse.builder()
                .number(request.getNumber())
                .objects(getObjects(request.getNumber())
                .build());
    }

    private Flux<MyObject> getObjects(int n) {
        // how to receive n strings from MyClient, make MyObject from each of them and then combine them together to a Flux/List?
    }
public class MyClient {
    public Mono<String> getName() {
        WebClient client = WebClient.builder().baseUrl(getUrl()).build();

        return client.get()
                // ...
                .retrieve()
                .bodyToMono(String.class);
    }

    private String getUrl() {
        // ...
    }
}

So, if i use Flux in MyResponse, i receive a response like:

{
  "number": 4,
  "objects": {
    "prefetch": 2147483647,
    "scanAvailable": true
  }
}

on the other hand, if i try to use a List, it seems to require blocking at some point, and i receive errors related to it. So, how do i do it?

Thanks in advance!


UPDATE: if i use collectList().block() to make a List out of Flux, i receive this:

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread <...>

As I understand from answers to this question, i should never block when my method returns Mono/Flux. Exctracting the block() call to a separate method which is called from the one returning Mono/Flux doesn't help. If i use share() before block(), then my request just executes forever, for some reason which i don't understand yet.


Solution

  • Alright, i made it.

    Flux as a field doesn't work in a desired way, so i need a List.

    public class MyResponse {
        private int number;
        private List<MyObject> objects;
        // ...
    }
    

    Now i need a way to make a List<MyObject> out of multiple Mono<String>s where each MyObject has a String field.

    The thing is that we don't ever get rid out of Mono or Flux, so we go for Flux<MyObject> first.

    private Flux<MyObject> getObjects(int n) {
        return Flux.range(0, n) // Flux<Integer>
                .map(i -> myClient.getName()) // Flux<String>
                .map(name -> new MyObject(name)); // Flux<MyObject>
    }
    

    and then we make Flux:

    private Mono<MyResponse> buildResponseBody(MyRequestBody request) {
        return getObjects(request.getNumber()) // Flux<MyObject>
                .collectList() // Mono<List<MyObject>>
                .map(objects -> MyResponse.builder() // Mono<MyResponse>
                        .number(request.getNumber())
                        .objects(objects)
                        .build()));
    }
    

    This way it works, as we don't have to block anything.

    The problem only appears when we want to get rid of Mono/Flux at some point, like if we want a pure List<MyObject>. But as long as we have a Mono and/or Flux as both input and output, we can do all the manipulations with the methods of these classes, preserving Mono or Flux at each stage.