Skip to content
Advertisement

Building a Recursive Data Structure with Spring WebFlux

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?

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.

Advertisement