比較過 Spring boot 的路由寫法,以及 ORM 寫法之後
今天我們來比較一下 Spring boot 在非同步需求下的寫法,和 Ktor 有什麼不同
Spring boot 處理非同步需求時,使用的設計模式是 Reactive Programming,基於 Project Reactor 建立了 Spring WebFlux 這個工具實現。
要實現單筆資料的非同步資料流,會使用到 Mono
這個類別。
一個存取多個第三方 API 的範例如下
import org.springframework.web.reactive.function.client.WebClient;
public Mono<String> getData() {
Mono<String> api1 = webClient.get().uri("http://api1.com/data").retrieve().bodyToMono(String.class);
Mono<String> api2 = webClient.get().uri("http://api2.com/data").retrieve().bodyToMono(String.class);
return Mono.zip(api1, api2, (data1, data2) -> data1 + data2);
}
如果是多筆資料,那麼可以使用 Flux
這個類別
@GetMapping("/all-data")
public Flux<String> getAllData() {
// 模擬三個不同 API
Flux<String> api1 = webClient.get()
.uri("https://api1.example.com/data")
.retrieve()
.bodyToFlux(String.class);
Flux<String> api2 = webClient.get()
.uri("https://api2.example.com/data")
.retrieve()
.bodyToFlux(String.class);
Flux<String> api3 = webClient.get()
.uri("https://api3.example.com/data")
.retrieve()
.bodyToFlux(String.class);
// 合併三個 Flux
return Flux.concat(api1, api2, api3);
}
這邊可以看到,由於 Java 語法的特性,所以當我們需要作非同步時,在建立暫存物件(Mono、Flux 等等),都會需要再加上一個宣告的流程。
如果這邊使用 Kotlin,可以再簡化一點點
@GetMapping("/all-data")
fun getAllData() = Flux.concat(
webClient.get()
.uri("https://api1.example.com/data")
.retrieve()
.bodyToFlux(String::class.java),
webClient.get()
.uri("https://api2.example.com/data")
.retrieve()
.bodyToFlux(String::class.java),
webClient.get()
.uri("https://api3.example.com/data")
.retrieve()
.bodyToFlux(String::class.java)
)
和上面的寫法不同,由於 Ktor 利用 Kotlin 內建的 coroutine 處理非同步流程,所以語法上可以直接使用語言支援的 async()
和 await()
來實作
get("/all-data") {
val client = HttpClient(CIO)
val allData = coroutineScope {
val deferred1 = async { client.get("https://api1.example.com/data") }
val deferred2 = async { client.get("https://api2.example.com/data") }
val deferred3 = async { client.get("https://api3.example.com/data") }
deferred1.await().bodyAsText() + deferred2.await().bodyAsText() + deferred3.await().bodyAsText()
}
client.close()
call.respond(allData)
}
雖然這邊的 async
也需要回傳一個暫存類別,實際上是 Deferred<HttpResponse>
,不過由於 Kotlin 的 smart casting,所以不需要特別知道 async
回傳的類別是什麼,我們只需要知道 async
拿到的東西要到 await
時才會實際拿到即可。需要學習的部分比起 Spring boot 的作法要少,並且語法也相對簡潔很多。
今天的部分就分享到這邊,我們明天見!