iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
Software Development

每天一點 Ktor 3.0:一個月學會 Kotlin 後端開發系列 第 17

Day 17:Spring boot 和 Ktor 的非同步存取 API 寫法

  • 分享至 

  • xImage
  •  

比較過 Spring boot 的路由寫法,以及 ORM 寫法之後

今天我們來比較一下 Spring boot 在非同步需求下的寫法,和 Ktor 有什麼不同

Reactive programming

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 的比較

和上面的寫法不同,由於 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 的作法要少,並且語法也相對簡潔很多。

今天的部分就分享到這邊,我們明天見!


上一篇
Day 16:Kotlin 和 Java 比較:Spring boot 篇
下一篇
Day 18:Quarkus 的特性和搭配的 ORM
系列文
每天一點 Ktor 3.0:一個月學會 Kotlin 後端開發20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言