超媒體客戶端

Carson Gross

通常,當我們在關於 線上討論 中,對於 REST & HATEOAS 吹毛求疵時,我們會說類似這樣的話:

JSON 不是超媒體,因為它沒有超媒體控制項。

看看這個 JSON

{
 "account": {
   "account_number": 12345,
   "balance": {
     "currency": "usd",
     "value": 50.00
   },
   "status": "open"
 }
}

看到了嗎?沒有超媒體控制項。

所以這個 JSON 不是超媒體,因此,回傳這個 JSON 的 API 就不是 RESTful 的。

對於這一點,偶爾會有聰明且經驗豐富的網頁開發人員回覆類似這樣的話:

好吧,REST 先生,那這個 JSON 呢?

{
  "account": {
    "account_number": 12345,
    "balance": {
      "currency": "usd",
      "value": 50.00
    },
    "status": "open",
    "links": {
      "deposits": "/accounts/12345/deposits",
      "withdrawals": "/accounts/12345/withdrawals",
      "transfers": "/accounts/12345/transfers",
      "close-requests": "/accounts/12345/close-requests"
    }
  }
}

看吧,現在這個回應中有超媒體控制項了(一般人稱之為連結,順帶一提),所以這個 JSON 就是超媒體。

所以這個 JSON API 現在是 RESTful 的了。感覺好些了嗎?

😑

我們必須承認,至少在高層次上,我們在線上的對手在這裡確實有些道理:這些看起來確實是超媒體控制項,而且它們實際上在 JSON 回應中。所以,你不能說這個 JSON 回應是 RESTful 的嗎?

由於本性固執,我們仍然不願意沒有一兩個 事實上 就承認這一點

等等:這就是在網路上關於 REST 的技術爭論如此特別的原因。

然而,這裡有一個更深層次的 事實上,它不是關於 *JSON API* 本身的,而是關於電線的另一端:接收 JSON 的客戶端

#超媒體客戶端與呈現資訊

這個針對非 RESTful JSON API 提出的修正方案,其更深層次的問題在於,為了讓這個 JSON 回應能夠適當地參與到 超媒體系統 中,消耗 JSON 的客戶端也需要滿足 RESTful 架構風格對整個系統施加的 約束

特別是,客戶端需要滿足 統一介面,在統一介面中,客戶端程式碼對於回應的「形狀」或細節一無所知,除了能夠向使用者顯示給定的超媒體。在一個正常運作的 RESTful 系統中,不允許客戶端有任何關於特定超媒體表示所代表的領域的「頻外」知識。

讓我們再次看看這個建議的 JSON 作為超媒體

{
  "account": {
    "account_number": 12345,
    "balance": {
      "currency": "usd",
      "value": 50.00
    },
    "status": "open",
    "links": {
      "deposits": "/accounts/12345/deposits",
      "withdrawals": "/accounts/12345/withdrawals",
      "transfers": "/accounts/12345/transfers",
      "close-requests": "/accounts/12345/close-requests"
    }
  }
}

現在,這個 API 的客戶端可以使用通用演算法將這個 JSON 轉換為例如一些 HTML。它可以透過客戶端樣板語言來做到這一點,例如,該樣板語言可以迭代 JSON 物件的所有屬性。

但這裡有一個問題:請注意,JSON 回應中沒有太多呈現資訊。它是相關帳戶的相當原始的資料表示,並帶有一些額外的 URL。

一個想要滿足 REST 的統一介面約束的客戶端,沒有太多關於如何向使用者呈現此資料的資訊。因此,客戶端需要採用非常基本的方法來向終端使用者顯示此帳戶。

它最終可能會是大致一組名稱/值對和一組用於操作的通用按鈕或連結,對吧?

在保持對 JSON 回應的形式不可知的情況下,它根本無法做更多事情。

#進一步推動我們的 JSON API

我們可以透過讓我們的 JSON API 更精緻並開始包含更多關於如何佈局資訊的資訊來解決這個問題:也許可以指示應強調或隱藏某些欄位等等。

但這只會是故事的一部分。

我們還需要更新客戶端以正確解釋 JSON API 的這些新元素。所以我們不再只是 API 設計師:我們也開始進入超媒體客戶端創建的業務。或者,更可能的是,我們正在要求我們的 API 客戶也進入超媒體客戶端的業務。

現在,Mike Amundsen 撰寫了一本關於如何構建正確的通用超媒體客戶端的 優秀書籍。但是你在這本書中會看到,創建一個好的超媒體客戶端並非易事,而且,這肯定不是大多數工程師會建立來消耗 JSON API 的東西,即使 JSON API 在他們的回應中具有越來越精緻的超媒體控制項和呈現資訊。

#「低效」表示

當我們開始考慮向 JSON 回應新增更多資訊時,Roy Fielding 的論文中的一段話浮現在腦海中

但權衡的結果是,統一介面會降低效率,因為資訊是以標準化形式傳輸,而不是針對應用程式需求的特定形式。

-Roy Fielding, https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5

對 HTML 的批評是,它將「呈現」資訊與「語意」資訊混合在一起。這通常與典型 JSON API 回應的簡潔性形成不利的對比。

然而,事實證明,正是這種呈現資訊,以及網路瀏覽器(即超媒體客戶端)將其轉換為人類可以互動的 UI 的能力,使得 HTML 作為更大的超媒體系統(即網路)的元件,能夠如此良好地運作。

這正是我們發現自己正在添加到我們自己的 JSON API 中,以支援正確的超媒體客戶端。

#建構超媒體客戶端

所以,你可以看到,僅在 JSON API 回應中提供超媒體控制項是不夠的。它是 REST 故事的一部分,但不是整個故事。而且,我逐漸了解,它並不是故事中真正困難的部分。事實上,創建超媒體客戶端是困難的部分,而創建一個好的超媒體客戶端是真正困難的部分

現在,我們都習慣了網路瀏覽器就在那裡,但請思考一下,僅僅為了在正常的日常網路請求中將 HTML 解析並呈現給終端使用者,就需要多少技術投入。這非常複雜。

這就是為什麼,如果我們要建立基於網路的 超媒體驅動應用程式,使用標準的、基於網路的超媒體客戶端(瀏覽器)可能是個好主意。

它已經是一個非常強大、經過良好測試的超媒體客戶端。而且,在一些幫助下,它甚至可以成為更好的超媒體客戶端。

一般而言,建構一個滿足 REST 所有約束的好的超媒體客戶端很困難,我們應該傾向於使用(和擴展)現有的客戶端,而不是建構我們自己的新客戶端。

#Hyperview

話雖如此,有時建構一個新的超媒體客戶端是適當的。例如,這就是像 Hyperview 這樣的技術如此令人印象深刻和特別的原因。Hyperview 不僅為新的、對行動裝置友好的超媒體 HXML 提供了規範。

它還為開發人員提供了一個了解如何呈現 HXML 的超媒體客戶端

如果沒有該超媒體客戶端,Hyperview 將只是一個理論上的超媒體,就像上面的 JSON 一樣,而不是一個引人注目、實用且完整的 RESTful 超媒體解決方案。

沒有超媒體客戶端的超媒體就像沒有腳踏車的魚,只是這條魚真的很擅長騎腳踏車。

#結論

我花了很多時間才意識到客戶端對於一個正確的 RESTful 超媒體系統有多重要。這是可以理解的,因為大多數早期關於 REST 的討論都是圍繞 API 設計,而客戶端根本沒有被提及。

我現在看到的是,很多這些討論都是本末倒置:只有當 RESTful 超媒體 API 被正確的超媒體客戶端消耗時,它才能變得有用。否則,你的超媒體控制項就會浪費在最終只想要完成任務的領域特定厚客戶端上。

此外,你的超媒體 API 幾乎肯定必須攜帶相當多的呈現層資訊才能使整個東西可用。事實證明,理查森成熟度模型的「第三級」,超媒體控制項,不足以達到「REST 的榮耀」。

在實務上,你需要加入一堆實用的呈現層技術,才能讓你的超媒體 API 真正運作,而且你需要一個正確構建的超媒體客戶端來消耗它。

當我寫 HATEOAS 是給人類用的 時,我對此有一個初步的感覺,但我當時沒有意識到客戶端/網路瀏覽器有多特別。

REST 並不僅僅是關於 API:正如 Roy Fielding 在他的論文中明確指出的那樣,它是一個系統架構。

除了像 Mike 這樣的一些人之外,我們在很大程度上忽略了 REST 故事的更大(真的,大得多)的一部分。

Creating A Hypermedia Client Is Hard Joke
</>