在反应堆中流动多个物体的有效/正确方法是什么 [英] What is the efficient/proper way to flow multiple objects in reactor

查看:40
本文介绍了在反应堆中流动多个物体的有效/正确方法是什么的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是响应式编程的新手,我正在尝试构建一个接近真实的示例.

I am new to reactive programming and to get my hand on I am trying to build a near to real example.

当您看到反应堆教程时,它们会向您展示非常简单的示例,例如.

When you see reactor tutorials they show you very easy examples like.

return userRepository.findById(1);

或类似处理通量之类的东西打破棕色小狐狸";字符串并找到唯一的字母等.但大多数这些教程都坚持单个对象,不幸的是,我无法找到任何指导线或教程,它们显示并排示例以先在命令式然后在反应式中键入相同的代码,这就是为什么我看到很多响应式编程的新人面临很多学习问题.

or something like dealing with flux the break the "brown little fox" string and find unique letters etc. But mostly these tutorials stick to single object and unfortunately i am unable to find any guide lines or tutorial which show a side by side examples to type same code first in imperative then in reactive, thats why i see lots of new comers in reactive programming faces a lot of learning issues.

但我的观点是在现实生活应用程序中,我们处理多个对象,如下面我在 reactor 中编写的示例代码.为我仍在学习的糟糕代码道歉.

but my point is in real life applications we deals with multiple objects like below sample code I wrote in reactor. Apologies for bad code i am still learning.

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 对象开始映射这个对象中的查询字符串.然后我需要一些来自数据库的数据,等等反应链继续做更多的工作.现在考虑

consider above code first i started with UserRequest object to map querystring in this object. then i need some data from database and so on reactive chaining continue more works to do. Now consider

  • 来自第一个链接方法的 UserRequest 对象和
  • 我从数据库中获取的用户文档然后我做了更多的操作,但在链接结束时我需要这两个对象来处理最终响应.实现我在谷歌上找到的唯一方法是元组.但在那之后代码看起来更脏,因为在每个下一个操作符中我都必须做
tuple.getT()

tuple.getT2()

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

So finally i would like to ask is that the proper way or i am missing something here. Because i learned one thing in reactive that data flows nothing more but like in imperative in the middle of logic we got oh i need another variable/object so i define it on top and use it but in reactive after 5th or 6th operator when developer realize ohh i need that object too here that was i created in 2nd operator then i have to go back and pass that in chaining to get in my 5th or 6th operator is that a proper way to do that.

推荐答案

通常有两种策略可以用来避免元组地狱",有时是孤立的&有时串联:

There's generally two strategies that can be used to avoid "tuple hell", sometimes in isolation & sometimes in tandem:

  • 使用您自己的自定义"更能描述类型的元组类(我几乎总是在生产代码中推荐这个,而不是使用内置的 Tuple 类);
  • 连接一些 map()/flatMap() 调用,这样就不需要声明元组了.
  • Use your own "custom" tuple class that's much more descriptive of types (I would nearly always recommend this in production code rather than using the built-in Tuple classes);
  • Concatenate some of your map() / flatMap() calls so that declaring tuples isn't required.

此外,还有更多规则需要牢记,可以在此处提供帮助:

In addition, there's more rules to bear in mind that can help things in general here:

  • 除非别无选择,否则永远不要改变反应链中的对象 - 使用具有 @With 的不可变对象 模式代替;
  • 不要使用多个链接在一起的 map() 调用来返回相同的类型 - 最好在单个 map 调用中完成所有操作;
  • 将长反应链的可重用元素分出到单独的方法中,并使用 map()flatMap() 将它们嵌入到你的主反应链中变换().
  • Never mutate objects in a reactive chain unless you have no other choice - use immutable objects with the @With pattern instead;
  • Don't use multiple map() calls chained together for returning the same type - favour doing everything in a single map call instead;
  • Farm reusable elements of a long reactive chain out to separate methods, and embed them in your main reactive chain using map(), flatMap() or transform().

如果我们将上面的例子付诸实践,我们可以将前三个映射调用转化为一个填充"方法.用户对象,使用 @With 样式而不是 setter(尽管您可以在此处使用 setter,如果真的必须):

If we take the above examples into practice, we can farm the first three map calls out into a single method that "populates" the user object, using the @With style rather than setters (though you can use setters here if you really must):

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,而是创建一个单独的类 - 我们称之为 VerifiedUser - 这将获取 userRequestuser 对象.然后,此类型还可以负责生成响应 cookie 对象,并通过简单的 getter 提供它.(我将编写 VerifiedUser 任务作为作者的练习 - 这应该非常简单.)

We can also farm out the part of the chain that looks up a user from the database. This part likely will need some form of new type, but instead of a Tuple, create a separate class - let's call it VerifiedUser - which will take the userRequest and user objects. This type can then also be responsible for generating the response cookie object, and providing it via a simple getter. (I'll leave writing the VerifiedUser task as an exercise for the author - that should be pretty trivial.)

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

We'd then have a method like this:

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,它是一个 named 容器类型,它更具描述性 &比 Tuple 有用.这种类型还为我们提供了一个 cookie 值.

So now we have two separate, small methods, which each take on a single responsibility. We also have another simple type, VerifiedUser, which is a named container type that's much more descriptive & useful than a Tuple. This type also gives us a cookie value.

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

This process has meant our main reactive chain can now become very simple indeed:

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

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

The end result is a chain that's safer (since we're not mutating a value in the chain, everything is kept immutable), much clearer to read, and much easier to extend in the future should we ever need to. If we need to go further then we could as well - if the methods created here needed to be used elsewhere for instance, they could easily be farmed out as spring beans conforming to a functional interface, then injected at will (and easily unit tested.)

(顺便说一句,您当然是对的,在撰写本文时,有很多琐碎的教程,但很少有深入"或现实世界"的材料.这通常是使用相当新的框架的情况下,但这肯定会使它们难以掌握,并导致大量无法维护的代码在野外!)

(As an aside, you're certainly correct that, at the time of writing, there's plenty of trivial tutorials but very little "in-depth" or "real-world" material out there. Such is often the case with reasonably new frameworks, but it certainly makes them hard to master, and results in lots of unmaintainable code out there in the wild!)

这篇关于在反应堆中流动多个物体的有效/正确方法是什么的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆