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?
Advertisement
Answer
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.