[英]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.