[英]Java Reactive stream how to map an object when the object being mapped is also needed on the next step of the stream
我正在使用 Java 11 和项目 Reactor(来自 Spring)。 我需要对 rest api 进行 http 调用(我只能在整个流程中调用一次)。 有了响应,我需要计算两件事:
在伪代码中是这样的:
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;
}
问题是,calculateValue 需要 http.getData 的返回值,但这已经在第一个 flatMap 上使用了,但我们还需要我们从之前的 flatMap 获得的文档 object。
我尝试使用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))
})
}
但这是执行httpClient.getData(id)
两次,这违背了我只调用一次的约束。 我明白为什么要执行两次(我订阅了两次)。
也许我的解决方案设计可以在某个地方改进,但我看不到在哪里。 对我来说,这在设计响应式代码时听起来像是一个“正常”问题,但到目前为止我找不到合适的解决方案。
我的问题是,如何以反应性和非阻塞方式完成此流程,并且只调用一次 rest api?
附言; 我可以在一个 map 中添加所有逻辑,但这将迫使我订阅 map 内的 Mono 之一,这是不推荐的,我想避免采用这种方法。
关于@caco3 评论的编辑我需要在 map 中订阅,因为getDocument
和calculateValue
方法都返回Mono
。
所以,如果我想把所有的逻辑放在一个单一的 map 中,它会是这样的:
public void computeData(String id) {
httpClient.getData(id)
.map(data -> getDocument(data).subscribe(s -> calculateValue(s, data)))
.subscribe();
}
您不必在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()
归结起来,您的问题类似于:
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));
}
(愚蠢的例子,我知道。)
问题是map()
(以及扩展的flatMap()
)是转换- 您可以访问新值,而旧值消失。 因此,在您的第二个flatMap()
调用中,您可以访问1 is an integer
,但不是原始值( 1
。)
这里的解决方案是将 map 映射到包含原始值和新值的某种合并结果,而不是映射到新值。 Reactor 为此提供了一个内置类型 - Tuple
。 因此,编辑我们的原始示例,我们将拥有:
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));
}
您可以使用相同的策略来“保留” document
和data
- 无需内部订阅或任何类似的东西:-)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.