【问题标题】:How to chain reactive operations after empty Mono without blocking?如何在空 Mono 之后链接反应操作而不阻塞?
【发布时间】:2021-05-09 22:31:43
【问题描述】:

基本上,我想要实现的是调用第二个存储库(ReactiveCrudRepository)或抛出异常,具体取决于调用第一个存储库的结果。

我最初的想法是这样的:

/** Reactive with blocking code */
public Flux<SecondThing> getThings(String firstThingName) {
    FirstThing firstThing = firstRepo
        .findByName(firstThingName)
        // Warning: "Inappropriate blocking method call"
        .blockOptional()  // this fails in test-context
        .orElseThrow(() -> new FirstThingNotFound(firstThingName));

    return secondRepo.findAllByFirstThingId(firstThing.getId());
}

这对应于以下非反应性方法:

/** Non-reactive */
public List<SecondThing> getThings(String firstThingName) {
    FirstThing firstThing = firstRepo
        .findByName(firstThingName)
        .orElseThrow(() -> new FirstThingNotFound(firstThingName));

    return secondRepo.findAllByFirstThingId(firstThing.getId());
}

我还没有找到一种以反应式非阻塞方式执行此操作的方法。如果第一次调用出现空的Mono,我只需要抛出一个错误,如果不为空则继续管道;但我似乎无法在这里正确使用onErrorStopdoOnError,并且map 没有帮助,因为它跳过了空的Mono

如果我使用id 而不是name,我有一个解决方法,但我对它不太满意,因为它在FirstThing 的实例但没有@ 的情况下表现出不同的行为987654332@链接到它:

/** Reactive workaround 1 */
public Flux<SecondThing> getThings(Long firstThingId) {
    return secondRepo
        .findAllByFirstThingId(firstThingId)
        .switchIfEmpty(
            Flux.error(() -> new FirstThingNotFound(firstThingName))
        );
}

我发现的另一种解决方法如下,它将空的 Mono 替换为 null 值,但它看起来不正确并且也会引发警告:

/** Reactive workaround 2 */
public Flux<SecondThing> getThings(String firstThingName) {
    return firstRepo
        .findByName(firstThingName)
        // Warning: "Passing 'null' argument to parameter annotated as @NotNull"
        .defaultIfEmpty(null)
        .flatMapMany(
            firstThing -> secondRepo.findAllByFirstThingId(firstThing.getId()
        )
        .onErrorMap(
            NullPointerException.class, e -> new FirstThingNotFound(firstThingName)
        );
}

将调用链接到两个存储库的正确方法是什么,以便FirstThing 与请求的firstThingName 的存在或不存在决定对第二个存储库的调用?

【问题讨论】:

  • 没有仔细看,但请注意,如果反应流为空,则反应流不会继续。所以,在我看来,您可以只调用 get firstThingName,如果它返回为空,则不会调用 getFirstThing,所以有问题吗?
  • @K.Nicholas 问题是,我想在没有找到 FirstThing 时抛出异常。所以我想有人可以将我的问题改写为:“如何从空的 Mono 映射到错误?”
  • 好吧,如果 firstRepo 返回一个 Optional 那么你可以做 'orElseThrow' 。我认为这是最干净的答案。 .map(op-&gt;op.orElseThrow(()-&gt;new IllegalArgumentException("name not found")))
  • @K.Nicholas 同意,但 ReactiveCrudRepository 返回 Mono 并将其转换为 Optional 需要阻塞操作,我试图避免这种操作。您的建议需要Mono&lt;Optional&lt;FirstThing&gt;&gt; 才能工作。在我的情况下这是不可能的,因为存储库类是自动生成的,但我会把这个想法留到其他时间。

标签: reactive-programming spring-webflux project-reactor


【解决方案1】:

我找到了一个如此简单的解决方案,以至于我可能会为没有早点找到它而感到羞耻:

public Flux<SecondThing> getThings(String firstThingName) {
    return firstRepo
        .findByName(firstThingName)
        .switchIfEmpty(Mono.error(() -> new FirstThingNotFound(firstThingName)))
        .flatMapMany(
            firstThing -> secondRepo.findAllByFirstThingId(firstThing.getId()
        );
}

诀窍在于switchIfEmpty 不会强制您选择“有效”替换值,因此可以使用Mono.error 直接传播正确的异常。

【讨论】:

  • 我正要写这个答案....我想知道你为什么要把事情复杂化这么多。所以你知道 null 不是 webflux 中允许的值,所以你不应该传递 null,理论上你应该永远不需要执行任何 nullchecks
  • @K.Nicholas 选项在 webflux 中通常很少需要,如果您返回一个选项,您可能正在处理传统的阻塞代码。我更喜欢返回 Mono&lt;T&gt; alt 的响应式代码。 Mono&lt;Void&gt; 代表 Maybe 而不是 Mono&lt;Optional&lt;T&gt;&gt;,这对我来说感觉像是不必要的双重包装。
  • @Toerktumlare 是的,现在我因为没有早点找到这个而感到愚蠢:D 我想向陌生人解释这个问题有助于解决它! “null 不是 webflux 中允许的值”是什么意思?这是为了避免以后出现问题而遵循的约定,还是我的编译器会给我带来严重的麻烦?
  • 反应流规范不允许序列中的空值,所以我不建议返回空值,我不知道 Reactor 遵循规范有多难,但你可能不会有意外行为的好时光如果你返回空值。从函数 (Mono&lt;Void&gt;) 中返回 Mono&lt;T&gt;Mono.empty() 是一种好习惯
  • @Toerktumlare 知道了。感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-25
  • 1970-01-01
  • 2019-02-23
  • 2020-12-26
  • 2013-08-08
相关资源
最近更新 更多