Построение рекурсивной структуры данных с помощью Spring WebFlux

#java #recursion #reactive-programming #spring-webflux #project-reactor

Вопрос:

У меня есть API REST, созданный с помощью платформы Spring WebFlux , и у меня есть конечная точка, которая возвращает a Flux<ChannelResponse> , где ChannelResponse находится древовидный объект, как показано ниже:

 public record ChannelResponse(
        long id,
        List<ChannelResponse> children
) {}
 

Теперь у меня нет большого опыта работы с парадигмой реактивного программирования, но именно так я бы реализовал такую конечную точку с синхронной логикой, чтобы каждый канал верхнего уровня (те, у которых нет родителя) преобразовывался в дерево ChannelResponse объектов:

 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);
}
 

Очевидно, что это не сработает в WebFlux, потому что я пытаюсь выполнить блокировку вызова репозитория.

Есть ли способ создать эту рекурсивную структуру данных асинхронным способом? Или, если нет, каковы мои варианты смешивания синхронного и асинхронного кода для достижения этого результата?

Комментарии:

1. Взгляните на expand и expandDeep операторов.

2. @Ikatiforis, я посмотрел expand , но, похоже, он используется для обхода рекурсивной структуры данных для получения плоского набора результатов. Это не то, чего я пытаюсь достичь.

Ответ №1:

Мне удалось решить эту проблему несколько странным способом, который заключался в том, чтобы использовать expandDeep оператора для создания плоского списка всех каналов, отсортированного таким образом, чтобы за каждым родителем немедленно следовал их набор детей. Затем я использовал простой синхронный рекурсивный метод для преобразования этих данных в нужный формат:

 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;
}
 

Я чувствую, что это решение не является оптимальным, хотя, поскольку оно требует очень специфического понимания того, как упорядочен список каналов, чтобы создать древовидную структуру. Пожалуйста, дайте мне знать, если есть более чистый способ сделать это.