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.
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.