efficient/proper 在 reactor 中流动多个 objects 的方法是什么

What is the efficient/proper way to flow multiple objects in reactor

我是响应式编程的新手,为了亲身体验,我正在尝试构建一个接近真实的示例。

当您看到 reactor 教程时,它们会向您展示非常简单的示例,例如。

return userRepository.findById(1);

或诸如处理通量之类的东西打破“棕色小狐狸”字符串并找到独特的字母等。但大多数这些教程坚持单一 object 不幸的是我找不到任何指南或教程它并排显示了先在命令式中键入相同代码然后在响应式中键入相同代码的示例,这就是为什么我看到很多响应式编程的新手面临很多学习问题。

但我的观点是在现实生活中我们处理多个 objects 就像下面我在 reactor 中编写的示例代码。抱歉代码不好,我还在学习。

public Mono<ServerResponse> response(ServerRequest serverRequest) {

        return
                Mono.just(new UserRequest())
                        .map(userRequest -> {
                            Optional<String> name = serverRequest.queryParam("name");
                            if (name.isPresent() && !name.get().isEmpty()) {
                                userRequest.setName(name.get());
                                return userRequest;
                            }
                            throw new RuntimeException("Invalid name");
                        })
                        .map(userRequest -> {
                            Optional<String> email = serverRequest.queryParam("email");
                            if (email.isPresent() && !email.get().isEmpty()) {
                                userRequest.setEmail(email.get());
                                return userRequest;
                            }
                            throw new RuntimeException("Invalid email");
                        })
                        .map(userRequest -> {
                            userRequest.setUuid(UUID.randomUUID().toString());
                            return userRequest;
                        })
                        .flatMap(userRequest ->
                                userRepository
                                        .findByEmail(userRequest.getEmail())
                                        .switchIfEmpty(Mono.error(new RuntimeException("User not found")))
                                        .map(user -> Tuples.of(userRequest, user))
                        )
                        .map(tuple -> {
                            String cookiePrefix = tuple.getT2().getCode() + tuple.getT1().getUuid();
                            return Tuples.of(tuple.getT1(), tuple.getT2(), cookiePrefix);
                        })
                        //Some more chaining here.
                        .flatMap(tuple ->
                                ServerResponse
                                        .ok()
                                        .cookie(ResponseCookie.from(tuple.getT3(), tuple.getT2().getRating()).build())
                                        .bodyValue("Welcome")
                        );

    }

首先考虑上面的代码,我从 UserRequest object 开始在这个 object 中映射查询字符串。 然后我需要来自数据库的一些数据等等反应链继续更多的工作要做。 现在考虑

tuple.getT()
tuple.getT2()

所以最后我想问的是正确的方法还是我在这里遗漏了一些东西。因为我在反应式中学到了一件事,即数据仅在逻辑中间流动,就像命令式一样,我们得到了哦,我需要另一个 variable/object 所以我在顶部定义它并使用它,但在第 5 或第 6 个运算符之后反应时开发人员意识到哦,我在这里也需要 object,这是我在第二个运算符中创建的,然后我必须返回并在链接中传递它以进入我的第 5 个或第 6 个运算符,这是一种正确的方法。

通常有两种策略可用于避免“元组地狱”,有时单独使用,有时同时使用:

  • 使用您自己的“自定义”元组 class,它对类型的描述性更强(我几乎 总是 在生产代码中推荐它,而不是使用内置的Tuple classes);
  • 连接一些 map() / flatMap() 调用,这样就不需要声明元组了。

此外,还有更多需要牢记的规则,它们通常可以帮助解决以下问题:

  • 除非你别无选择,否则永远不要改变反应链中的对象 - 改用 @With 模式的不可变对象;
  • 不要使用链接在一起的多个 map() 调用来返回相同的类型 - 最好在单个映射调用中完成所有操作;
  • 将长反应链中的可重用元素集中到单独的方法中,并使用 map()flatMap()transform().
  • 将它们嵌入到您的主要反应链中

如果我们将上述示例付诸实践,我们可以将前三个 map 调用集中到一个“填充”用户对象的方法中,使用 @With 样式而不是 setter(尽管您可以在此处使用 setter如果你真的必须的话):

private UserRequest populateUser(UserRequest userRequest, ServerRequest serverRequest) {
    return userRequest
            .withName(serverRequest.queryParam("name")
                    .filter(s -> !s.isEmpty())
                    .orElseThrow(() -> new RuntimeException("Invalid name")))
            .withEmail(serverRequest.queryParam("email")
                    .filter(s -> !s.isEmpty())
                    .orElseThrow(() -> new RuntimeException("Invalid email")))
            .withUuid(UUID.randomUUID().toString());
}

我们还可以将链中从数据库中查找用户的部分外包出去。这部分可能 需要某种形式的新类型,但不是 Tuple,而是创建一个单独的 class - 让我们称之为 VerifiedUser - 这将获取 userRequestuser 对象。这种类型还可以负责生成响应 cookie 对象,并通过简单的 getter 提供它。 (我将编写 VerifiedUser 任务作为作者的练习 - 这应该很简单。)

然后我们会有这样的方法:

private Mono<VerifiedUser> lookupUser(UserRequest userRequest) {
    return userRepository
            .findByEmail(userRequest.getEmail())
            .map(user -> new VerifiedUser(userRequest, user)) //VerifiedUser can contain the logic to produce the ResponseCookie
            .switchIfEmpty(Mono.error(new RuntimeException("User not found")));
}

所以现在我们有两个独立的小方法,每个方法都承担一个责任。我们还有另一个简单类型,VerifiedUser,它是一个 命名的 容器类型,比 Tuple 更具描述性和实用性。这种类型也给了我们一个 cookie 值。

这个过程意味着我们的主要反应链现在可以变得非常简单:

return Mono.just(new UserRequest())
        .map(userRequest -> populateUser(userRequest, serverRequest))
        .flatMap(this::lookupUser)
        .flatMap(verifiedUser ->
                ServerResponse.ok()
                        .cookie(verifiedUser.getCookie())
                        .bodyValue("Welcome")
        );

最终结果是 更安全 的链(因为我们没有改变链中的值,所以一切都保持不变),更清晰易读,也更容易将来 扩展 。如果我们需要更进一步,那么我们也可以——如果这里创建的方法需要在其他地方使用,例如,它们可以很容易地作为符合功能接口的 spring bean 被出租,然后随意注入(和轻松进行单元测试。)

(顺便说一句,您肯定是正确的,在撰写本文时,有很多琐碎的教程,但很少有“深入”或“真实世界”material。相当新的框架通常就是这种情况,但这肯定会使它们难以掌握,并导致大量无法维护的代码在野外出现!)