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.