简体   繁体   English

Java Reactive stream how to map an object when the object being mapped is also needed on the next step of the stream

[英]Java Reactive stream how to map an object when the object being mapped is also needed on the next step of the stream

I am using Java 11 and project Reactor (from Spring).我正在使用 Java 11 和项目 Reactor(来自 Spring)。 I need to make a http call to a rest api (I can only make it once in the whole flow).我需要对 rest api 进行 http 调用(我只能在整个流程中调用一次)。 With the response I need to compute two things:有了响应,我需要计算两件事:

  1. Check if a document exists in the database (mongodb).检查数据库(mongodb)中是否存在文档。 If it does not exists then create it and return it.如果它不存在,则创建它并返回它。 Otherwise just return it.否则就退货。
  2. Compute some logic on the response and we are done.计算一些关于响应的逻辑,我们就完成了。

In pseudo code it is something like this:在伪代码中是这样的:

public void computeData(String id) {
    httpClient.getData(id) // Returns a Mono<Data>
        .flatMap(data -> getDocument(data.getDocumenId()))
         // Issue here is we need access to the data object consumed in the previous flatMap but at the same time we also need the document object we get from the previous flatMap
        .flatMap(document -> calculateValue(document, data)) 
        .subscribe();
}

public Mono<Document> getDocument(String id) {
    // Check if document exists
    // If not create document

    return document;
}

public Mono<Value> calculateValue(Document doc, Data data) {
    // Do something...
    return value;
}

The issue is that calculateValue needs the return value from http.getData but this was already consumed on the first flatMap but we also need the document object we get from the previous flatMap.问题是,calculateValue 需要 http.getData 的返回值,但这已经在第一个 flatMap 上使用了,但我们还需要我们从之前的 flatMap 获得的文档 object。

I tried to solve this issue using Mono.zip like below:我尝试使用Mono.zip解决此问题,如下所示:

public void computeData(String id) {
    final Mono<Data> dataMono = httpClient.getData(id);

    Mono.zip(
        new Mono<Mono<Document>>() {
            @Override
            public void subscribe(CoreSubscriber<? super Mono<Document>> actual) {
                final Mono<Document> documentMono = dataMono.flatMap(data -> getDocument(data.getDocumentId()))
                actual.onNext(documentMono);
            }
        },
        new Mono<Mono<Value>>() {
            @Override
            public void subscribe(CoreSubscriber<? super Mono<Value>> actual) {
                actual.onNext(dataMono);
            }
        }
    )
    .flatMap(objects -> {
        final Mono<Document> documentMono = objects.getT1();
        final Mono<Data> dataMono = objects.getT2();

        return Mono.zip(documentMono, dataMono, (document, data) -> calculateValue(document, data))
    })
}

But this is executing the httpClient.getData(id) twice which goes against my constrain of only calling it once.但这是执行httpClient.getData(id)两次,这违背了我只调用一次的约束。 I understand why it is being executed twice (I subscribe to it twice).我明白为什么要执行两次(我订阅了两次)。

Maybe my solution design can be improved somewhere but I do not see where.也许我的解决方案设计可以在某个地方改进,但我看不到在哪里。 To me this sounds like a "normal" issue when designing reactive code but I could not find a suitable solution to it so far.对我来说,这在设计响应式代码时听起来像是一个“正常”问题,但到目前为止我找不到合适的解决方案。

My question is, how can accomplish this flow in a reactive and non blocking way and only making one call to the rest api?我的问题是,如何以反应性和非阻塞方式完成此流程,并且只调用一次 rest api?

PS;附言; I could add all the logic inside one single map but that would force me to subscribe to one of the Mono inside the map which is not recommended and I want to avoid following this approach.我可以在一个 map 中添加所有逻辑,但这将迫使我订阅 map 内的 Mono 之一,这是不推荐的,我想避免采用这种方法。

EDIT regarding @caco3 comment I need to subscribe inside the map because both getDocument and calculateValue methods return a Mono .关于@caco3 评论的编辑我需要在 map 中订阅,因为getDocumentcalculateValue方法都返回Mono

So, if I wanted to put all the logic inside one single map it would be something like:所以,如果我想把所有的逻辑放在一个单一的 map 中,它会是这样的:

public void computeData(String id) {
    httpClient.getData(id)
        .map(data -> getDocument(data).subscribe(s -> calculateValue(s, data)))
        .subscribe();
}

You do not have to subscribe inside map , just continue building the reactive chain inside the flatMap :您不必在map内订阅,只需继续在flatMap内构建反应链:

getData(id) // Mono<Data>
        .flatMap(data -> getDocument(data.getDocumentId()) // Mono<Document>
                .switchIfEmpty(createDocument(data.getDocumentId())) // Mono<Document>
                .flatMap(document -> calculateValue(document, data)) // Mono<Value>
         )
        .subscribe()

Boiling it down, your problem is analogous to:归结起来,您的问题类似于:

Mono.just(1)
        .flatMap(original -> process(original))
        .flatMap(processed -> I need access to the original value and the processed value!
                System.out.println(original); //Won't work
        );


private static Mono<String> process(int in) {
    return Mono.just(in + " is an integer").delayElement(Duration.ofSeconds(2));
}

(Silly example, I know.) (愚蠢的例子,我知道。)

The problem is that map() (and by extension, flatMap() ) are transformations - you get access to the new value, and the old one goes away.问题是map() (以及扩展的flatMap() )是转换- 您可以访问值,而旧值消失。 So in your second flatMap() call, you've got access to 1 is an integer , but not the original value ( 1 .)因此,在您的第二个flatMap()调用中,您可以访问1 is an integer ,但不是原始值( 1 。)

The solution here is to, instead of mapping to the new value, map to some kind of merged result that contains both the original and new values.这里的解决方案是将 map 映射到包含原始值和新值的某种合并结果,而不是映射到新值。 Reactor provides a built in type for that - a Tuple . Reactor 为此提供了一个内置类型 - Tuple So editing our original example, we'd have:因此,编辑我们的原始示例,我们将拥有:

Mono.just(1)
        .flatMap(original -> operation(original))
        .flatMap(processed -> //Help - I need access to the original value and the processed value!
                System.out.println(processed.getT1()); //Original
                System.out.println(processed.getT2()); //Processed

                ///etc.
        );


private static Mono<Tuple2<Integer, String>> operation(int in) {
    return Mono.just(in + " is an integer").delayElement(Duration.ofSeconds(2))
            .map(newValue -> Tuples.of(in, newValue));
}

You can use the same strategy to "hold on" to both document and data - no need for inner subscribes or anything of the sort:-)您可以使用相同的策略来“保留” documentdata - 无需内部订阅或任何类似的东西:-)

如何转换列表<object>至 Map<k, v> 使用 java stream<div id="text_translate"><p> 我想将List&lt;ObjectInList&gt;转换为Map&lt;K, V&gt;</p><pre> class ObjectInList { List&lt;Long&gt; listWithLong; Map&lt;String, Object&gt; dataMap; // there is 'id' key, and i want to use this id as key in map to be converted }</pre><p> 新的 map 格式如下</p><pre>String type; // this type is value of dataMap. List&lt;Long&gt; contents</pre><p> List&lt;Object&gt;中的每个 Object 都可以有重复的类型</p><p>例如</p><pre>///////// before converted //////////// [ { list: [1,2,3], dataMap: { type: "a", } }, { list: [4,5,6], dataMap: { type: "b", } }, { list: [7,8], dataMap: { type: "a", } }, ] ///////////// after converted ////////// { "a": [1,2,3,7,8], "b": [4,5,6] }</pre></div></k,></object> - How to convert LIst<Object> to Map<K, V> with using java stream

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在反应式Java中将新对象添加到现有流中? - How can I add in reactive Java a new Object to an existing stream? 如何在Java8中按流在Map对象上按时间段对对象求和? - How to sum object by time period on a Map object by stream in Java8? 在java 8中访问从第一个流到下一个流api的对象引用 - Access object reference from first stream to next stream api in java 8 java 8流-如何在映射后获取原始对象引用 - java 8 stream - how to get original object reference after map 如何使用 Java Stream Map 列表<src>到 Object Tgt?</src> - How to use Java Stream Map a List<Src> to an Object Tgt? 如何获取地图<String, List<Object> &gt; 来自 Java 8 中的流 - How get a Map<String, List<Object>> from a Stream in Java 8 如何转换列表<object>至 Map<k, v> 使用 java stream<div id="text_translate"><p> 我想将List&lt;ObjectInList&gt;转换为Map&lt;K, V&gt;</p><pre> class ObjectInList { List&lt;Long&gt; listWithLong; Map&lt;String, Object&gt; dataMap; // there is 'id' key, and i want to use this id as key in map to be converted }</pre><p> 新的 map 格式如下</p><pre>String type; // this type is value of dataMap. List&lt;Long&gt; contents</pre><p> List&lt;Object&gt;中的每个 Object 都可以有重复的类型</p><p>例如</p><pre>///////// before converted //////////// [ { list: [1,2,3], dataMap: { type: "a", } }, { list: [4,5,6], dataMap: { type: "b", } }, { list: [7,8], dataMap: { type: "a", } }, ] ///////////// after converted ////////// { "a": [1,2,3,7,8], "b": [4,5,6] }</pre></div></k,></object> - How to convert LIst<Object> to Map<K, V> with using java stream java-stream,map,与对象无关的键 - java-stream, map, keys not related to object 使用Java8流将Object减少为Map - Use Java8 stream to reduce Object to a Map 使用Java流将对象映射到多个对象 - Map object to multiple objects using Java stream
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM