【问题标题】:How to perform an action only if the Mono is empty and throw an error if not empty仅当 Mono 为空时如何执行操作,如果不为空则抛出错误
【发布时间】:2020-01-27 08:13:01
【问题描述】:

我正在尝试将项目转换为使用 Spring WebFlux,但在使一些基本业务逻辑正常工作时遇到了问题。我有一个负责检索/保存记录的存储库层和一个负责应用程序业务规则的服务层。我想要做的(在服务层)是检查给定用户名的用户是否已经存在。如果是这样,我想回复一个错误。如果没有,我想允许插入发生。

我在存储库层调用了一个方法,该方法将通过用户名查找用户,如果未找到,它将返回一个空的 Mono。这按预期工作;但是,我尝试了 flatMap 和 (defaultIfEmpty 和 swithIfEmpty) 的各种组合,但无法编译/构建。

    public Mono<User> insertUser(User user) {
        return userRepository.findByUsername(user.username())
            .flatMap(__ -> Mono.error(new DuplicateResourceException("User already exists with username [" + user.username() + "]")))
            .switchIfEmpty(userRepository.insertUser(user));
    }

我得到的错误是 Mono&lt;Object&gt; cannot be converted to Mono&lt;User&gt;,所以 swithIfEmpty 似乎没有反映适当的类型,并且转换似乎也不起作用。

【问题讨论】:

  • 你用的是什么编译器?
  • 您使用什么来将数据保存在存储库中?
  • 我正在使用 Java 11 JDK 构建。持久化是在 Postgresql 中完成的。但是,我现在只是想让一个单元测试适用于这个逻辑,所以存储库层被模拟了。

标签: java spring spring-boot spring-webflux


【解决方案1】:

经过额外的测试,并考虑到其他开发人员的反馈,我找到了以下解决方案:

    public Mono<User> insertUser(User user) {
        return userRepository.findByUsername(user.username())
            .flatMap(__ -> Mono.error(new DuplicateResourceException("User already exists with username [" + user.username() + "]")))
            .switchIfEmpty(Mono.defer(() -> userRepository.insertUser(user)))
            .cast(User.class);
    }

正如 Thomas 所说,编译器感到困惑。我的假设是因为flatMap 返回一个带有错误的 Mono,而 switchIfEmpty 返回一个带有用户的 Mono,因此它恢复为带有 Object 的 Mono(因此额外的 .cast 运算符让它编译) .

另一个添加是在switchMap 中添加Mono.defer。否则,switchIfEmpty 总是在触发。

我仍然对其他建议/替代方案持开放态度(因为这似乎是一种相当普遍的需求/模式)。

【讨论】:

  • 嗨@cgaskill!为什么要使用 Mono.defer 方法?
【解决方案2】:

您收到此编译器错误的原因如下。

flatmap 获取已完成的Mono 中的内容,并尝试将其转换为它可以推断的任何类型。 Mono.error 包含一个类型,该类型属于Object

一种方法可能是将您的逻辑移动到平面图中。

// This is just example code using strings instead of repos
public Mono<String> insertUser(String user) {
    return Mono.just(user)
            // Here we map/convert instead based on logic
            .flatMap(__ -> {
                if (__.isEmpty())
                    return Mono.error(new IllegalArgumentException("User already exists with username [" + user + "]"));
                return Mono.just(user);
            }).switchIfEmpty(Mono.just(user));
}

switchIfEmpty 不适合做出合乎逻辑的决定,恕我直言。文档说明

如果此单声道已完成,则回退到备用单声道 没有数据

如果我们没有得到任何东西,这更像是对其他东西的后备,因此我们可以保持数据流继续进行。

你也可以

Mono.empty().doOnNext(o -> {
        throw new IllegalArgumentException("User already exists with username [" + o + "]");
    }).switchIfEmpty(Mono.just("hello")).subscribe(System.out::println);

【讨论】:

  • flatMap 将返回通过调用userRepository.findUserByUsername 找到的用户,它没有像字符串那样的isEmpty 方法。我的期望是,如果对userRepository.findUserByUsername 的调用简单地完成而不发出用户,则flatMap 中的代码甚至不会被调用,switchIfEmpty(或defaultIfEmpty)中的代码将被调用,这将允许我插入用户(因为它们不存在)。
  • ofc 你的用户没有isEmpty 方法,这不是重点。您可以改为空检查。如果完成但返回空,则不会进入平面地图。但是如果它完成并且有一个值,编译器怎么知道你想要返回什么,你现在告诉它返回Mono&lt;Object&gt;。但是编译器怎么知道你的意图呢? flatMap 需要能够推断返回类型,并且在您的代码中,您告诉它推断 Mono 的返回类型。你必须像编译器一样思考,编译器是怎么知道的?好吧它没有。
  • 但是,我的解决方案是在数据库中的字段上放置一个唯一约束,然后您不需要在应用程序中执行所有这些逻辑,您总是尝试保存,如果保存失败,将错误返回给客户端。
  • 如果您在数据库中执行实际删除,这将起作用。我们的过程是永不删除(我们不喜欢丢失数据),因此我们执行逻辑删除(我们停用记录)。所以我们不能有一个唯一的约束,因为我们希望允许另一个用户使用相同的用户名(只要它是非活动的)。所以只有 1 条具有该用户名的活动记录。
【解决方案3】:

我正在使用具有类型参数E 的抽象类,所以我不能使用.cast(E.class)。我们的解决方案是

private Mono<E> checkIfStatementExists(E statement) {
    return this.statementService.getByStatementRequestId(statement.getStatementRequestId())
            .flatMap(sr -> Mono.<E>error(new ValidationException("Statement already exists for this request!")))
            .switchIfEmpty(Mono.just(statement));
}

我想我需要在下周与我的同事讨论这件事。

编辑。我们和同事讨论过,更新的代码在上面。

【讨论】:

    【解决方案4】:

    遇到同样的问题,结果在下面做,

    请注意:这不需要类型转换并且适用于两种情况,即

    • 如果元素已存在于数据库中,则抛出错误。

    • 插入元素,否则返回 Mono。

        public Mono<UserDTO> insertUser(User user) {
           return this.userRepository.findByUsername(user.getUsername())
                   .flatMap(foundUser-> null != foundUser ? Mono
                           .error(new UserAlreadyExistsException(ExceptionsConstants.USER_ALREADY_EXISTS_EXCEPTION))
                           : Mono.just(user))
                   .switchIfEmpty(this.userRepository.save(user))
                   .map(userCreated -> copyUtils.createUserDTO(userCreated));
        }
      

    【讨论】:

      猜你喜欢
      • 2013-11-14
      • 1970-01-01
      • 2017-07-09
      • 1970-01-01
      • 2023-03-26
      • 1970-01-01
      • 2020-08-20
      • 2019-01-01
      • 1970-01-01
      相关资源
      最近更新 更多