【问题标题】:Update objects's state in Reactor在 Reactor 中更新对象的状态
【发布时间】:2021-05-05 09:10:23
【问题描述】:

给定以下方法:

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)

【问题讨论】:

  • 正如下面的答案中所指出的,这取决于。如果您的代码示例是安全的。很确定。它是否使代码易于遵循。我会说不。这使得很难理解您的对象处于什么状态,并且如果在某个地方做错了,状态可能会以一种可能导致难以发现错误和问题的方式发生变化。我会避免它,但这就是我,我会尝试在设计一切时考虑到不变性。

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


【解决方案1】:

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

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

但是,如果有问题的方法是从受良好控制的上下文中调用的,那么它可能是安全的。

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

【讨论】:

    【解决方案2】:

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

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

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

    这导致了“风格”方面。

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

    因此,考虑到这一点,如果我正在查看此代码,我强烈希望 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,并期望从这些参数返回一个新的UserProfileUpdate 对象;
    • UserProfileUpdate 需要使用 @With (lombok) 或类似的实现来增强,以允许它生成自身的不可变副本,只更改一个值。
    • 其他代码可能需要进行类似的修改,以将 profileUpdate 封装为反应链的一部分,而不是依赖于对其进行修改。

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

    【讨论】:

    • 感谢您的详细回答。我的profileUpdate 实例包含一些Map 引用:Map&lt;ByteBuffer, byte[]&gt; attributes。您是否建议也保持该地图不可变?
    • @Lefteris 理想情况下是的——它应该一直是不可变的,这样你就可以保证你不会在任何地方产生讨厌的副作用。当然,根据情况可能比您愿意付出的更多,我只是将答案描绘成一个理想的视角:-)
    猜你喜欢
    • 1970-01-01
    • 2020-09-30
    • 1970-01-01
    • 2018-09-08
    • 2020-05-02
    • 2020-07-01
    • 2022-01-06
    • 1970-01-01
    • 2021-06-26
    相关资源
    最近更新 更多