技術專欄

集結國內外精選文章,掌握最新雲端技術新知與應用 

iKala Cloud / 部落格 / 應用程式現代化 / API 設計:瞭解 gRPC、OpenAPI 和 REST 以及使用它們的時機(上)

API 設計:瞭解 gRPC、OpenAPI 和 REST 以及使用它們的時機(上)

大多數軟體開發者都知道 API 的設計主要有兩種模式,RPC 和 REST。無論採用哪種模式,大多數現代 API 都是透過那樣的方式對應到相同的 HTTP 通訊協定來實作的。在 RPC API 設計中,保持 RPC 模型的同時,採用 HTTP 中的一兩個想法也變得很常見,這就增加了 API 設計者可以做的選擇。本篇文章將分別解釋這些選擇,並教您如何進行選擇。

gRPC 是一種實作 RPC API 的技術,它使用 HTTP 2.0 作為其底層傳輸協定。gRPC 是基於遠端程式呼叫 (Remote Procedure Call, RPC) 模型,其中可定址實體是過程,資料隱藏在過程後面。而 HTTP 的工作方式與此相反。在 HTTP 中,可定址實體是 「資料實體」(在 HTTP 規範中稱為 「資源」),而行為隱藏在資料後面—系統的行為是透過建立、修改和刪除資源而產生的。

事實上,在 Google 和其它地方建立的許多 API 以一種有趣的方式結合了 RPC 和 HTTP 的一些想法。這些 API 和 HTTP 一樣,採用了實體導向的模型,但都是使用 gRPC 來定義和實作的,而且所產生的 API 可以使用標準的 HTTP 技術來使用。我們將嘗試描述一下這種方式是如何工作的,為什麼它可能對你有好處以及它可能在哪些地方沒有好處。

首先,讓我們先仔細看看 HTTP 通常是如何應用於 API 的。

使用 HTTP 進行 API 的三種主要方式

大多數公共 API 和許多私有分散式 API 都使用 HTTP 作為傳輸方式,至少部分原因是組織習慣於處理 HTTP 在 80 和 443 Port 上的流量安全問題。

建立使用 HTTP 的 API 有三種重要且獨特的方法。它們分別是:

  1. REST
  2. gRPC(和Apache Thrift等)
  3. OpenAPI(及其競爭對手)

REST

最不常用的 API 模型是 REST—只有少數 API 是這樣設計的,儘管 REST 這個詞被更廣泛使用(或說是被濫用)。這種風格的 API 代表性功能之一,就是客戶端不從其它資訊中建立 URL—他們只能使用伺服器傳遞出來的 URL。這就是瀏覽器的工作方式—它不會從碎片中建立出它所使用的 URL,也不理解它所使用的 URL 的特定網頁格式;它只是盲目地遵循從伺服器接收到的目前頁面中找到的 URL,或從以前的頁面中加入頁籤的 URL,或者使用者輸入的 URL。瀏覽器對 URL 的唯一解析是擷取發送 HTTP 請求所需的資訊,而瀏覽器對 URL 的唯一建立是由相對 URL 和基礎 URL 組成絕對 URL。如果你的 API 是 REST API,那麼你的客戶端永遠不需要理解你的 URL 的格式,那些格式也不是給客戶端的 API 規範的一部分。

REST API 可以非常簡單。很多額外的技術已經被發明出來與 REST API 一同應用,例如JSON API、ODATA、HAL、Siren 或 JSON Hyper-Schema 等,但你不需要這些技術也能做好 REST。

gRPC 

gRPC 是第二種使用 HTTP 來做 API 的模型,gRPC 使用的是 HTTP/2,但 HTTP 並沒有暴露給 API 設計者。gRPC 產生的 Stub 和框架也向客戶端和伺服器隱藏了 HTTP,因此無需擔心 RPC 概念是如何對應到 HTTP 的,他們只需要學習 gRPC 便足夠。

客戶端須透過以下三個步驟使用 gRPC API:

  1. 決定要使用哪個過程來 call
  2. 計算出要使用的參數值(若有)
  3. 使用程式碼產生的 Stub 進行使用,傳遞參數值

OpenAPI

最可能使用 HTTP 的 RPC API 的方式是使用像OpenAPI這樣的規範語言(以前稱為 Swagger規範)。

OpenAPI 風格的 API 的一個代表性功能是,客戶端透過從其它資訊中建立 URL 來使用 API。客戶端使用 OpenAPI 要透過以下三個步驟:

  1. 決定使用哪個 OpenAPI 的 URL 路徑範本
  2. 計算出要使用的參數值(若有)
  3. 將參數值插入 URL 路徑範本中,並發送一個 HTTP 請求

應該馬上就能看出,這樣子執行的 API 不是 REST API。OpenAPI 使用 HTTP 的方法會要求客戶端對他們在請求中使用的 URL 的格式有詳細的瞭解,並從其它資訊中建立出符合該格式的 URL。這與 REST API 的工作方式恰恰相反。在 REST API 那種情況下,客戶端完全不知道他們使用的 URL 的格式,也不需要建立這些格式。OpenAPI 所支援的模型是非常流行和成功的,也是 API 設計者最重要的選擇之一,事實上,OpenAPI 模型不是 REST 這件事,並沒有降低它的實用性和重要性。

第二,你可能會發現,使用 OpenAPI API 的客戶端模型與使用 gRPC API 的客戶端模型非常相似。gRPC 客戶端選擇一個過程來使用,OpenAPI 客戶端選擇一個 URL 路徑範本來使用;當 gRPC 客戶端使用 Stub 步驟將參數與過程簽章相結合並進行使用時,OpenAPI 客戶端將參數值插入到 URL 路徑範本中並發出 HTTP 請求。雖然細節不同,但整體模式非常相似。OpenAPI 還包含了一些工具,可以選擇在客戶端程式設計語言中產生客戶端 Stub 過程,隱藏這些細節,使得兩者的客戶端體驗更加相似。

解釋 gRPC 和 OpenAPI 的客戶端模型之間的密切相似性的一種方法是,將 OpenAPI 看作是一種指定的經典的 RPC API 的語言,並對 HTTP 請求進行自訂對應。如此一來,那麼 gRPC 和 OpenAPI 都是 RPC 介面定義語言 (interface definition languages, IDL),它們之間的本質區別在於,OpenAPI 將底層 HTTP 傳輸的細節暴露給客戶端,並允許 API 設計者控制對應,而 gRPC 使用預定義的對應,隱藏了所有的 HTTP 細節。

即便您或許不認同 OpenAPI 所使用的基本 API 模型就是老式的 RPC,也很難否認這兩者之間有一些明顯的相似之處,而且它們都有別於 REST。不管是哪種方式,我認為這些相似之處都有助於闡述以下更詳細的比較。

每一種方法都有各有優缺點—我們將探討這三種方法,並提供您一些想法,幫你決定最適合你的應用。

簡單明瞭的 RPC

這裡有篇熱門的部落格文章說明了 RPC 的優點(我們稍後再來看這篇部落格文章):

createAccount(username, contact_email, password) -> account_id
addSubscription(account_id, subscription_type) -> subscription_id
sendActivationReminderEmail(account_id) -> null
cancelSubscription(subscription_id, reason, immediate=True) -> null
getAccountDetails(account_id) -> {full data tree}

 

這位部落客認為,很多人覺得定義一個 RPC API 來解決這個問題很容易,但卻苦苦思索如何用 HTTP 來解決同樣的問題。浪費了大量的時間和精力,卻沒有意識到對自己的專案有任何好處。原因之一是在 HTTP 之上設計一個 API 是一個必須要學會的技能,而且有很多選擇。

使用 REST 模型也很簡單明瞭

由於我們已經設計了很多使用 REST 的 API,所以如何用 REST 來表達這個例子似乎也是一目了然。以下作法提供參考:

POST /accounts  (username, contact_email, password)> -> account_URL
POST /subscriptions  (account_URL, subscription_type) -> subscription_URL
POST /activation-reminder-outbox  (account_URL) -> email_URL
POST /cancellations  (subscription_URL, reason, immediate=True) -> cancellaton_URL
GET {account_URL} ->  {full data tree}

 

客戶端提供的 username、contact_email、password、account_URL 和其它的資料都只是簡單的 JSON 鍵值對應。這邊沒有提到標題中的細節,也沒有提到結果如何返回,因為這一切在 HTTP 規範中都有解釋,並不能自由選擇或決定。

客戶端和伺服器之間雙向傳遞的所有識別字 (idnetifiers) 都是 URL—在 API 中沒有任何識別字不是 URL。每當一個資源包含對另一個資源的引用時,該引用就會使用另一個資源的 URL 來表示。這種技術被稱為「Hypertext」或「Hypermedia」—如果你的 API 沒有以這種方式使用 URL,那麼它就不是使用 REST 模型,因為 Hypertext 連結是 REST 有別於其它模型的一個功能特性。RPC API 也透過在另一個實體中包含一個實體的識別字來表達實體之間的關係,但這些識別不是可以直接使用的 URL,而不需要額外的資訊。

REST 的優勢

REST 所宣稱的優勢基本上都是網際網路本身的優勢,比如穩定性、統一性和普遍性。這些優點在其它地方都有記載,而且 REST 是少數人感興趣的,所以我們在這裡就不多做說明。有一個例外是 HTTP/REST 模型中固有的實體導向性,這個特性是特別值得關注的,因為它已經被 gRPC 和 OpenAPI 等非 REST模 型的支援者廣泛討論和採用。

根據筆者個人經驗,相較簡單的 RPC 模型,實體導向的模型 (entity-oriented models) 更簡單、更有規律、更容易理解,而且隨著時間的推移會更穩定。RPC API 往往會隨著一個又一個過程的加入而有所成長,每一個過程都實作了系統可以執行的動作。

實體導向的模型為系統的行為提供了一個整體的組織架構。例如,我們都熟悉的網上購物的實體模型,有產品、購物車、訂單、帳戶等,都是實體模型。如果這個能力只用 RPC 過程來表達,那麼就會產生一長串非架構化的過程,包括瀏覽商品目錄、增加到購物車、結帳、追蹤發貨、退貨等,這些過程都是一長串非架構化的。

這個列表很快就會變得不堪負荷,而且程式定義之間很難實作一致性。把這個清單架構化的一種方法是為每個實體類型使用一個標準的過程集來建模所有的行為。HTTP 本質上是實體導向的,但你也可以在 RPC 中加入實體導向的內容,這一點將在後面討論,按照實體類型對過程進行分組也是物件導向語言的關鍵思想之一。

如何使用 OpenAPI

在 OpenAPI 中,你定義的東西叫做路徑。在 YAML 中,OpenAPI 的路徑是這樣的:

paths:
  /pets/{petId}:
    get:
      operationId: getPetById
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          schema:
            type: string

 

像這樣定義路徑的 API 在 API 中的不同地方向客戶端暴露了 {petId} 的值,並要求客戶端使用適當的路徑定義,以便將 {petId} 值(和其它值)轉換為可以在 HTTP 請求中使用的 URL。

以這種方式表達和使用ID,是對REST的簽章想法中超文字連結的一種替代。

OpenAPI 將這些路徑中的變數稱為「參數」,路徑和 HTTP 方法的組合稱為操作—類似於 RPC 系統的術語。

OpenAPI 使用帶參數的 URL 範本可以被看作是一種表達類似於 RPC 的概念的方式,透過自訂對應到 HTTP 的方式來表達。

OpenAPI 的優缺點

依筆者認為,OpenAPI 的成功有兩個基本特點。首先,OpenAPI 模型與大多數程式開發者所熟悉的傳統 RPC 模型相似,該模型也很符合他們所使用的程式設計語言的概念。第二個原因是,它允許程式開發者定義那些 RPC 概念與 HTTP 請求的自訂對應。而這第二個原因既帶來了好處,也帶來了問題。最主要的好處是,客戶端可以只使用標準的 HTTP 技術來存取 API,這對於公共 API 來說尤其重要,因為這代表著 API 可以從幾乎所有的程式設計語言和環境中存取,而不需要客戶端採用任何額外的技術。缺點是,它可能需要花費大量的精力來設計 HTTP 細節—網上有很多關於應該做什麼和不應該做什麼的指引,但筆者認為很多是互相牴觸的,故此需要進一步努力探討。

在本篇文章的下半部,我們將繼續探討 gRPC 的優缺點、它與 OpenAPI 的比較,以及更多的使用情境。

(原文翻譯改編自 Google Cloud。)

分享本文:
FacebookLineTwitter
回到頂端