GraphQL 入門:為什麼我們需要一種新的 API(第 1 部分)

已發表: 2022-03-10
快速總結↬與其先探索特性,不如將它們置於上下文中並了解它們是如何存在的。 GraphQL 簡介和過去 60 年 API 開發的經驗教訓。

在本系列中,我想向您介紹 GraphQL。 最後,您不僅應該了解它是什麼,還應該了解它的起源、缺點以及如何使用它的基礎知識。 在第一篇文章中,我不想跳入實現,而是想通過查看從過去 60 年的 API 開發(從 RPC 到現在)中吸取的經驗教訓,回顧一下我們是如何以及為什麼會使用 GraphQL(和類似工具)的。 畢竟,正如馬克吐溫所描述的那樣,沒有新的想法。

“沒有新想法這回事。這是不可能的。我們只是把很多舊想法放在一個心理萬花筒裡。”

——馬克吐溫在“馬克吐溫自己的自傳:北美評論的章節”中

但首先,我必須向房間裡的大象講話。 新事物總是令人興奮,但它們也會讓人感到筋疲力盡。 您可能聽說過 GraphQL,只是想:“為什麼……”或者,您可能想的更像是,“我為什麼要關心新的 API 設計趨勢? 休息……很好。” 這些都是合理的問題,所以讓我幫助解釋為什麼你應該關注這個問題。

介紹

必須權衡為您的團隊帶來新工具的好處與其成本。 有很多東西要衡量。 學習需要時間,轉換從功能開發中帶走的時間,以及維護兩個系統的開銷。 由於成本如此之高,任何新技術都必須更好、更快或更高效。 增量改進雖然令人興奮,但不值得投資。 我想談論的 API 類型,特別是 GraphQL,在我看來是向前邁出的一大步,並且提供了足夠多的好處來證明成本是合理的。

與其先探索功能,不如將它們置於上下文中並了解它們是如何存在的。 為此,我將首先回顧一下 API 的歷史。

跳躍後更多! 繼續往下看↓

RPC

RPC 可以說是第一個主要的 API 模式,它的起源可以追溯到 60 年代中期的早期計算。 當時,計算機仍然如此龐大和昂貴,以至於我們認為 API 驅動的應用程序開發的概念大多只是理論上的。 帶寬/延遲、計算能力、共享計算時間和物理鄰近性等限制迫使工程師考慮分佈式系統而不是暴露數據的服務。 從 60 年代的 ARPANET,一直到 90 年代中期的 CORBA 和 Java 的 RMI,大多數計算機都使用遠程過程調用 (RPC) 相互交互,這是一種客戶端-服務器交互模型,客戶端導致一個過程(或方法)在遠程服務器上執行。

RPC 有很多優點。 它的主要原則是允許開發人員在遠程環境中處理代碼,就好像它在本地環境中一樣,儘管速度要慢得多且可靠性較低,這會在其他不同和不同的系統中產生連續性。 像 ARPANET 的許多東西一樣,它領先於時代,因為這種類型的連續性是我們在處理數據庫訪問和外部服務調用等不可靠和異步操作時仍在努力的目標。

幾十年來,對於如何允許開發人員將這樣的異步行為嵌入到程序的典型流程中,已經進行了大量的研究。 如果當時有諸如 Promises、Futures 和 ScheduledTasks 之類的東西可用,我們的 API 環境可能看起來會有所不同。

RPC 的另一個優點是,由於它不受數據結構的限制,因此可以為客戶端編寫高度專業化的方法,這些方法可以準確地請求和檢索所需的信息,從而最大限度地減少網絡開銷和更小的負載。

然而,有些事情使 RPC 變得困難。 首先,連續性需要上下文。 RPC,按照設計,在本地和遠程系統之間創建了相當多的耦合——你失去了本地和遠程代碼之間的界限。 對於某些領域,這是可以的,甚至像在客戶端 SDK 中一樣首選,但對於客戶端代碼不太了解的 API,它可能比更面向數據的東西靈活得多。

不過,更重要的是API 方法的擴散潛力。 理論上,RPC 服務公開了一個可以處理任何任務的小型 API。 在實踐中,大量外部端點可以在沒有太多結構的情況下增加。 隨著團隊成員的來來去去和項目的轉變,隨著時間的推移,需要大量的紀律來防止 API 重疊和重複。

確實,通過適當的工具和文檔更改,就像我提到的那樣,可以管理,但是在我編寫軟件的時候,我遇到過很少的自動文檔和規範的服務,所以對我來說,這有點像紅鯡魚。

肥皂

出現的下一個主要 API 類型是 SOAP,它誕生於 90 年代末期的 Microsoft Research。 SOAP (簡單對象訪問協議)是一個雄心勃勃的協議規範,用於應用程序之間基於 XML 的通信。 SOAP 的目標是通過為複雜的 Web 服務創建結構良好的基礎來解決 RPC,特別是 XML-RPC 的一些實際缺陷。 實際上,這只是意味著向 XML 添加行為類型系統。 可悲的是,它造成的障礙比它解決的要多,這一點可以從今天很少有新的 SOAP 端點被編寫的事實證明。

“SOAP 是大多數人認為適度的成功。”

— 唐箱

SOAP 確實有一些好處,儘管它的冗長和可怕的名字令人難以忍受。 客戶端和服務器之間的 WSDL 和 WADL(發音為“wizdle”和“waddle”)中的可執行合同保證了可預測的、類型安全的結果,並且 WSDL 可用於生成文檔或創建與 IDE 和其他工具的集成。

SOAP 關於 API 演進的重大啟示是它逐漸且可能無意中引入了更多面向資源的調用。 SOAP 端點允許您請求具有預定結構的數據,而不是考慮生成數據所需的方法(假設它是以這種方式編寫的)。

SOAP 最顯著的缺點是過於冗長。 沒有大量工具幾乎是不可能使用的。 您需要工具來編寫測試、工具來檢查來自服務器的響應以及工具來解析所有數據。 許多較舊的系統仍然使用 SOAP,但對工具的要求使其對於大多數新項目來說過於繁瑣,而且 XML 結構所需的字節數使其成為服務移動設備或繁瑣的分佈式系統的糟糕選擇。

有關更多信息,值得閱讀 SOAP 規範以及來自原始團隊成員之一 Don Box 的 SOAP 令人驚訝的有趣歷史。

休息

最後,我們來到了當前的 API 設計模式:REST。 REST 由 Roy Fielding 在 2000 年的一篇博士論文中介紹,使鐘擺朝著完全不同的方向擺動。 REST 在很多方面都是 SOAP 的對立面,並排看它們會讓你覺得他的論文有點生氣了。

SOAP 使用 HTTP 作為啞傳輸,並在請求和響應正文中構建其結構。 另一方面,REST 拋棄了客戶端-服務器合同、工具、XML 和定制標頭,用 HTTPs 語義替換它們,因為它是結構選擇而不是使用 HTTP 動詞與引用某個層次結構中的資源的數據和 URI 交互數據。

肥皂休息
HTTP 動詞獲取、放置、發布、修補、刪除
數據格式XML 無論你想要什麼
客戶端/服務器合約每天一整天! 誰需要那些
類型系統JavaScript 有無符號短對嗎?
網址描述操作命名資源

REST 完全明確地將 API 設計從建模交互更改為簡單地建模域數據。 在使用 REST API 時完全面向資源,您不再需要知道或關心檢索給定數據需要什麼; 您也不需要了解有關後端服務實施的任何信息。

簡單性不僅對開發人員有利,而且由於 URL 代表穩定的信息,因此很容易緩存,它的無狀態性使其易於水平擴展,並且由於它對數據進行建模而不是預測消費者的需求,因此可以顯著減少 API 的表面積.

REST 很棒,它的普遍性取得了驚人的成功,但與之前的所有解決方案一樣,REST 也並非沒有缺陷。 為了具體談談它的一些缺點,讓我們看一個基本的例子。 假設我們必須構建一個博客的登錄頁面,該頁面顯示博客文章列表及其作者姓名。

基本博客的主頁
基本博客的主頁,顯示每篇文章的標題和作者。 (大預覽)

讓我們編寫可以從普通 REST API 檢索主頁數據的代碼。 我們將從包裝資源的幾個函數開始。

 const getPosts = () => fetch(`${API_ROOT}/posts`); const getPost = postId => fetch(`${API_ROOT}/posts/${postId}`); const getAuthor = authorId => fetch(`${API_ROOT}/authors/${authorId}`);

現在,讓我們來編排吧!

 const getPostWithAuthor = postId => { return getPost(postId) .then(post => getAuthor(post.author)) .then(author => { return Object.assign({}, post, { author }) }) }; const getHomePageData = () => { return getPosts() .then(postIds => { const postDetails = postIds.map(getPostWithAuthor); return Promise.all(postDetails); }) };

所以我們的代碼將執行以下操作:

  • 獲取所有帖子;
  • 獲取每個帖子的詳細信息;
  • 獲取每個帖子的作者資源。

好消息是,這很容易推理,組織良好,並且每個資源的概念邊界都繪製得很好。 令人遺憾的是,我們只發出了 8 個網絡請求,其中許多是串行發生的。

 GET /posts GET /posts/234 GET /posts/456 GET /posts/17 GET /posts/156 GET /author/9 GET /author/4 GET /author/7 GET /author/2

是的,您可以通過建議 API 可以有一個分頁的/posts端點來批評這個示例,但這會讓人毛骨悚然。 事實仍然是,您經常擁有一組 API 調用,這些調用相互依賴以呈現完整的應用程序或頁面。

開發 REST 客戶端和服務器肯定比之前的更好,或者至少是更白痴的證明,但是自從菲爾丁的論文以來的二十年裡發生了很多變化。 當時,所有的電腦都是米色的塑料; 現在它們是鋁的! 不過說真的,2000 年接近個人計算爆炸的頂峰。 處理器的速度每年都會翻一番,網絡也以令人難以置信的速度變得更快。 互聯網的市場滲透率在 45% 左右,無處可去,只能上升。

然後,在 2008 年左右,移動計算成為主流。 借助移動設備,我們在一夜之間在速度/性能方面有效地倒退了十年。 2017 年,我們擁有近 80% 的國內和超過 50% 的全球智能手機普及率,現在是重新思考我們對 API 設計的一些假設的時候了。

REST 的弱點

以下是從客戶端應用程序開發人員的角度批判性地看待 REST,尤其是在移動設備中工作的開發人員。 GraphQL 和 GraphQL 風格的 API 並不新鮮,也不能解決 REST 開發人員無法解決的問題。 GraphQL 最重要的貢獻是它能夠系統地解決這些問題,並且具有其他地方不容易獲得的集成水平。 換句話說,它是一個“包含電池”的解決方案。

REST 的主要作者,包括 Fielding,在 2017 年底發表了一篇論文(對 REST 架構風格的反思和“現代 Web 架構的原則設計”),反思了 REST 的兩個十年及其啟發的許多模式。 對於任何對 API 設計感興趣的人來說,這本書很簡短,絕對值得一讀。

借助一些歷史背景和參考應用程序,讓我們看看 REST 的三個主要弱點。

REST 很健談

REST 服務往往至少有點“健談”,因為它需要在客戶端和服務器之間進行多次往返才能獲得足夠的數據來呈現應用程序。 這種級聯的請求會對性能產生破壞性影響,尤其是在移動設備上。 回到博客示例,即使在使用新手機和具有 4G 連接的可靠網絡的最佳情況下,在下載第一個數據字節之前,您僅在延遲開銷上花費了近 0.5 秒。

55ms 4G 延遲 * 8 個請求 = 440ms 開銷

圖表說明用戶對應用性能的反應
描述用戶對不同應用性能級別的響應的圖表。 (圖片來源:高性能瀏覽器網絡)(大預覽)

聊天服務的另一個問題是,在許多情況下,下載一個大請求所需的時間比下載許多小請求所需的時間少。 小型請求的性能下降確實有很多原因,包括 TCP 慢啟動、缺乏標頭壓縮和 gzip 效率,如果您對此感到好奇,我強烈建議您閱讀 Ilya Grigorik 的高性能瀏覽器網絡。 MaxCDN 博客也有很好的概述。

這個問題在技術上不是 REST 而是 HTTP,特別是 HTTP/1。 無論 API 風格如何,HTTP/2 幾乎都解決了聊天問題,並且它在瀏覽器和原生 SDK 等客戶端中得到了廣泛的支持。 不幸的是,API 方面的推出速度很慢。 在排名前 10k 的網站中,截至 2017 年底,採用率約為 20%(並且還在攀升)。令我驚訝的是,即使是 Node.js,也在其 8.x 版本中獲得了 HTTP/2 支持。 如果你有能力,請更新你的基礎設施! 同時,讓我們不要停留,因為這只是等式的一部分。

撇開 HTTP 不談,最後一點為什麼聊天很重要,與移動設備的工作方式有關,特別是它們的無線電的工作方式。 總而言之,操作收音機是手機中最耗電的部分之一,因此操作系統會一有機會就將其關閉。 啟動收音機不僅會耗盡電池電量,還會為每個請求增加更多開銷。

TMI(過度獲取)

REST 樣式服務的下一個問題是發送方式比需要的信息多。 在我們的博客示例中,我們只需要每篇文章的標題和作者姓名,這僅是返回內容的 17% 左右。 對於非常簡單的有效載荷,這是 6 倍的損失。 在現實世界的 API 中,這種開銷可能是巨大的。 例如,電子商務網站通常將單個產品表示為數千行 JSON。 就像聊天問題一樣,REST 服務今天可以使用“稀疏字段集”來有條件地包含或排除部分數據來處理這種情況。 不幸的是,對網絡緩存的支持參差不齊、不完整或有問題。

工具和內省

REST API 缺少的最後一件事是自省機制。 如果沒有任何關於端點返回類型或結構信息的合同,就無法可靠地生成文檔、創建工具或與數據交互。 可以在 REST 中工作以不同程度地解決此問題。 完全實現 OpenAPI、OData 或 JSON API 的項目通常是乾淨的、指定良好的,並且在不同程度上有良好的文檔記錄,但這樣的後端很少見。 即使是超媒體,一個相對容易實現的成果,儘管幾十年來一直在會議演講中被吹捧,但如果有的話,仍然不經常做得很好。

結論

每種 API 類型都有缺陷,但每種模式都有缺陷。 這篇文章並不是對軟件巨頭已經奠定的驚人基礎的判斷,只是對這些模式中的每一個進行冷靜的評估,從客戶開發人員的角度以它們的“純粹”形式應用。 我希望你不要離開這種想法,比如 REST 或 RPC 模式被打破,而是思考它們各自是如何做出權衡的,以及工程組織可能集中精力改進自己的 API的領域。

在下一篇文章中,我將探索 GraphQL 以及它如何解決我上面提到的一些問題。 GraphQL 和類似工具的創新在於它們的集成水平,而不是它們的實現。 請,如果您或您的團隊不是在尋找“包含電池”的 API,請考慮研究像新的 OpenAPI 規範這樣的東西,它可以幫助今天建立更強大的基礎!

如果你喜歡這篇文章(或者如果你討厭這篇文章)並且想給我反饋,請在 Twitter 上以@ebaerbaerbaer 的身份找到我,或者在 ericjbaer 的 LinkedIn 上找到我。