[英]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:
有了响应,我需要计算两件事:
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 中订阅,因为
getDocument
和calculateValue
方法都返回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:-)您可以使用相同的策略来“保留”
document
和data
- 无需内部订阅或任何类似的东西:-)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.