在 Reactor 中更新对象的状态

Update objects's state in Reactor

给定以下方法:

private Mono<UserProfileUpdate> upsertUserIdentifier(UserProfileUpdate profileUpdate, String id){
    return userIdentifierRepository.findUserIdentifier(id)
            .switchIfEmpty(Mono.defer(() -> {
                profileUpdate.setNewUser(true);
                return createProfileIdentifier(profileUpdate.getType(), id);
            }))
            .map(userIdentifier -> {
                profileUpdate.setProfileId(userIdentifier.getProfileId());
                return profileUpdate;
            });
}

switchIfEmptymap 操作符改变 profileUpdate 对象。在 switchIfEmpty 运算符中进行变异是否安全?关于map,如果我没理解错的话,这是不安全的,对象profileUpdate一定是不可变的吧?例如:

private Mono<UserProfileUpdate> upsertUserIdentifier(UserProfileUpdate profileUpdate, String id){
        return userIdentifierRepository.findUserIdentifier(id)
                .switchIfEmpty(Mono.defer(() -> {
                    profileUpdate.setNewUser(true);
                    return createProfileIdentifier(profileUpdate.getType(), id);
                }))
                .map(userIdentifier -> profileUpdate.withProfileId(userIdentifier.getProfileId()));
    }

在链的后面,另一个方法改变了对象:

public Mono<UserProfileUpdate> transform(UserProfileUpdate profUpdate) {
        if (profUpdate.isNewUser()) {
            profUpdate.getAttributesToSet().putAll(profUpdate.getAttributesToSim());
        } else if (!profUpdate.getAttributesToSim().isEmpty()) {
            return  userProfileRepository.findUserProfileById(profUpdate.getProfileId())
                    .map(profile -> {
                        profUpdate.getAttributesToSet().putAll(
                                collectMissingAttributes(profUpdate.getAttributesToSim(), profile.getAttributes().keySet()));
                        return profUpdate;
                    });
        }
        return Mono.just(profUpdate);
    }

以上方法调用如下:

  Mono.just(update)
  .flatMap(update -> upsertUserIdentifier(update, id))
  .flatMap(this::transform)

模糊的答案,但是...这取决于!

在返回的 MonoFlux 中改变输入参数的危险来自这样一个事实,即 MonoFlux 可以被多次订阅。在这种情况下,突然间您手上有了 共享资源 ,这可能会导致令人费解的问题。

但是,如果所讨论的方法是从控制良好的上下文中调用的,则它是安全的。

在您的情况下,flatMap 确保内部发布者仅被订阅一次。因此,只要您 在此类 flatMap 中使用这些方法,它们就可以安全地改变其输入参数(它保留在 flatmapping 函数的范围内)。

这里可以说有两个因素 - “危险”方面和“风格”方面。

西蒙很好地涵盖了“危险”方面;不过,我要补充一件事。即使您的代码在我们可以在这里看到的方法中是“安全的”(由于我们对内部平面图发布者的单一订阅有保证),我们仍然不能绝对保证它在更广泛的上下文中是安全的 - 我们不不知道还有什么可以看到或可能改变您的 profileUpdate 参数。如果它被创建,立即传递给这个方法,然后在这个方法完成后读取,那么当然,它很好。如果它是在过去的某个时间点创建的,也许传递给一些可能会或可能不会改变它的方法,传回,传递到这个方法,传递到其他几个方法......那么,好吧,它 可能 是安全的,但分析和确定它变得越来越困难 - 如果它不安全,那么追踪由该行为导致的百万分之一的错误可能发生的位置也变得同样困难.

现在,您的代码可能看起来与我刚刚描述的这个复杂的混乱完全不同 - 但只是在这里和那里更改了几行,或者在传入之前在其他地方对该对象“再做一次修改”,它可能会开始到达那里。

这导致了“风格”方面。

就我个人而言,我非常喜欢尽可能将 一切 作为反应链的一部分。即使忽略不良突变/副作用的可能性,如果全部以这种方式编写,阅读代码也会变得更加困难——你必须在精神上跟踪通过链传递的值,以及链外部的值正在变异的链。对于这个例子,它相当微不足道,在更大的例子中,它变得几乎不可读(至少对我的大脑而言!)

所以考虑到这一点,如果我正在审查这段代码,我强烈希望 UserProfileUpdate 是不可变的,然后使用类似这样的代码:

private Mono<UserProfileUpdate> upsertUserIdentifier(UserProfileUpdate profileUpdate, String id){
    return userIdentifierRepository.findUserIdentifier(id)
            .switchIfEmpty(() -> createProfileIdentifier(profileUpdate.withNewUser(true), id))
            .map(userIdentifier -> profileUpdate.withProfileId(userIdentifier.getProfileId()));
}

...请注意,这不是直接替代品,特别是:

  • createProfileIdentifier() 只需要一个 UserProfileUpdate 对象和一个 id,并期望从这些参数中 return 一个新的 UserProfileUpdate 对象;
  • UserProfileUpdate 需要使用 @With (lombok) 或类似的实现来增强,以允许它生成自身的不可变副本,仅更改一个值。
  • 其他代码可能需要进行类似的修改,以将 profileUpdate 封装为反应链的一部分,而不是依赖于改变它。

但是,至少在我看来,生成的代码将更加健壮和可读。