I have a REST API that is built with the Spring WebFlux framework, and I have an endpoint which returns a Flux<ChannelResponse>
, where ChannelResponse
is a tree-structured object, as shown below:
public record ChannelResponse(
long id,
List<ChannelResponse> children
) {}
Now, I don't have much experience with the reactive programming paradigm, but this is how I would implement such an endpoint with synchronous logic, such that each top-level channel (those which have no parent) is transformed into a tree of ChannelResponse
objects:
public Flux<ChannelResponse> getAll() {
return channelRepository.findAllByParentChannelIdOrderByOrdinality(null)
.map(channel -> getChannelDataRecursive(channel));
}
private FullChannelResponse getChannelDataRecursive(Channel channel) {
var children = channelRepository.findAllByParentChannelIdOrderByOrdinality(channel.getId())
.collectList().block();
List<ChannelResponse> childData = new ArrayList<>();
for (var child : children) {
childData.add(getChannelDataRecursive(child));
}
return new ChannelResponse(channel.getId(), childData);
}
Obviously this won't work in WebFlux, because I am trying to do a blocking repository call.
Is there a way to produce this recursive data structure in an asynchronous way? Or if not, what are my options for mixing synchronous and asynchronous code to achieve this result?
CodePudding user response:
I was able to solve it in a somewhat strange way, which was to use the expandDeep
operator to produce a flat list of all channels, sorted such that each parent is followed immediately by their set of children. I then used a plain synchronous recursive method to transform this data into the desired format:
public Flux<ChannelResponse> getAll() {
return channelRepository.findAllByParentChannelIdOrderByOrdinality(null)
.expandDeep(channel -> channelRepository.findAllByParentChannelIdOrderByOrdinality(channel.getId()))
.collectList()
.flatMapMany(channels -> Flux.fromIterable(buildRecursiveChannelResponse(null, channels)));
}
public List<ChannelResponse> buildRecursiveChannelResponse(Long parent, List<Channel> channels) {
List<ChannelResponse> responses = new ArrayList<>();
while (!channels.isEmpty()) {
Channel c = channels.get(0);
if (!Objects.equals(c.getParentChannelId(), parent)) return responses;
channels.remove(0);
var children = buildRecursiveChannelResponse(c.getId(), channels);
responses.add(new ChannelResponse(c.getId(), children));
}
return responses;
}
I feel like this solution is not optimal though, since it requires a very specific understanding of how the list of channels is ordered in order to produce the tree structure. Please let me know if there is a cleaner way to do this.