使用 Spring WebFlux 构建递归数据结构
Building a Recursive Data Structure with Spring WebFlux
我有一个使用 Spring WebFlux 框架构建的 REST API,我有一个 returns 和 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 中不起作用,因为我正在尝试进行阻塞存储库调用。
有没有办法以异步方式生成这种递归数据结构?或者,如果不是,我有哪些混合同步和异步代码来实现此结果的选项?
我能够以一种有点奇怪的方式解决它,即使用 expandDeep
运算符生成所有频道的平面列表,排序使得每个 parent 紧随其后他们的一套children。然后,我使用一种简单的同步递归方法将这些数据转换为所需的格式:
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;
}
不过我觉得这个解决方案不是最优的,因为它需要非常具体地了解频道列表的排序方式才能生成树结构。如果有更简洁的方法,请告诉我。
我有一个使用 Spring WebFlux 框架构建的 REST API,我有一个 returns 和 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 中不起作用,因为我正在尝试进行阻塞存储库调用。
有没有办法以异步方式生成这种递归数据结构?或者,如果不是,我有哪些混合同步和异步代码来实现此结果的选项?
我能够以一种有点奇怪的方式解决它,即使用 expandDeep
运算符生成所有频道的平面列表,排序使得每个 parent 紧随其后他们的一套children。然后,我使用一种简单的同步递归方法将这些数据转换为所需的格式:
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;
}
不过我觉得这个解决方案不是最优的,因为它需要非常具体地了解频道列表的排序方式才能生成树结构。如果有更简洁的方法,请告诉我。