REST將會過時,而GraphQL則會長存

weixin_33763244發表於2018-08-19

本文最初釋出於Medium上freeCodeCamp的部落格站點,經原作者Samer Buna授權由InfoQ中文站翻譯並分享。

\\

在處理過多年的REST API之後,當我第一次學習到GraphQL以及它試圖要解決的問題時,我禁不住發了一條推文,這條推文的內容恰好就是本文的標題。

\\

5bd2393511b69e48e3fc6c8f1ba6d310.jpg

\\

當然,那個時候,我只是抱著好奇的心態進行了嘗試,但現在,我相信當時的戲言卻正在變成現實。

\\

請不要誤解,我並不是說GraphQL將會“殺死”REST或類似的斷定。REST可能永遠都不會消亡,就像XML永遠也不會滅亡一樣。我只是認為GraphQL之於REST,就像JSON之於XML那樣。

\\

本文不會100%地鼓吹GraphQL,這裡面會有一個很重要的章節討論GraphQL靈活性的代價。巨大的靈活性會帶來巨大的成本。

\\

簡而言之:為何要使用GraphQL?

\\

GraphQL能夠非常漂亮地解決三個重要的問題:

\\
  • 為了得到檢視所需的資料,需要進行多輪的網路呼叫:藉助GraphQL,要獲取所有的初始化資料,我們僅需一次到伺服器的網路呼叫。要在REST API中達到相同的目的,我們需要引入非結構化的引數和條件,這是很難管理和擴充套件的。\\t
  • 客戶端對服務端的依賴:藉助GraphQL,客戶端會使用一種請求語言,該語言:1)消除了伺服器端硬編碼資料形式或數量大小的必要性;2)將客戶端與服務端解耦。這意味著我們能夠獨立於伺服器端維護和改善客戶端。\\t
  • 糟糕的前端開發體驗:藉助GraphQL,開發人員只需使用一種宣告式的語言表達使用者的介面資料需求即可。他們所描述的是需要什麼資料,而不是如何得到這些資料。在GraphQL中,UI所需的資料以及開發人員描述資料的方式之間存在緊密的聯絡。\

本文將會詳細闡述GraphQL是如何解決這些問題的。

\\

在開始之前,有些人可能還不熟悉GraphQL,所以我們先給出一個簡單的定義。

\\

什麼是GraphQL?

\\

GraphQL是一門語言。如果我們將GraphQL傳授給一個軟體應用的話,這個應用能夠以宣告式的方式與同樣使用GraphQL的後端資料服務交流任意的資料需求。

\\
\

孩子能夠快速學習一門新的語言,而成人學起來就會更困難一些。與之類似,在一個新應用中從頭開始使用GraphQL要比將其引入到一個成熟的語言中更容易一些。

\
\\

要教會一個資料服務使用GraphQL語言,我們需要實現一個執行時層,並將其暴露給想要與服務通訊的客戶端。我們可以將伺服器端的這個層視為一個簡單的GraphQL翻譯器,或者是講GraphQL語言的代理,它代表了資料服務。GraphQL不是一個儲存引擎,所以它自己無法成為一個解決方案。這也是為什麼我們無法具備一個只使用GraphQL語言的伺服器,而是要實現一個轉換執行時的原因。

\\

這個執行時層,可以使用任何語言編寫,它定義了一個基於圖的通用模式(schema),該模式能夠釋出資料服務的能力(capabilities)。使用GraphQL語言的客戶端應用能夠在它的能力範圍內查詢該模式。這種方式將客戶端和服務端進行了解耦,允許它們都能獨立地演化和擴充套件。

\\

GraphQL可以是查詢(讀操作),也可以是變更(寫操作)。在這兩種情況下,請求都是一個簡單字串,該字串能夠被GraphQL服務解析、執行並以特定的格式解析資料。在移動和Web應用中,流行的響應格式是JSON

\\

什麼是GraphQL?(通俗講解版)

\\

GraphQL就是關於資料通訊的。我們有客戶端和伺服器端,它們之間都需要進行對話。客戶端需要告訴伺服器端它需要什麼資料,而伺服器端要以實際的資料滿足客戶端的需求。GraphQL就位於這種通訊之間。

\\

0c79bc6e4757d26940390563852ab46a.jpg
圖片來源於作者Pluralsight課程的截圖:使用GraphQL構建可擴充套件的API

\\

你可能會問,我們為什麼不能讓客戶端和伺服器端直接通訊呢?當然可以。

\\

有多個原因促使我們在客戶端和伺服器端之間放置一個GraphQL層。其中有個原因,可能也是最常見的,那就是效率。客戶端通常需要跟服務端要求多個資源,而服務端通常只能理解如何響應單個資源。所以,客戶端需要發起多輪請求,這樣才能收集到它需要的所有資料。

\\

藉助GraphQL,我們可以將這種多請求的複雜性轉移到服務端,讓GraphQL層來對其進行處理。客戶端對GraphQL層發起一個請求並且會得到一個響應,該響應中精確包含了客戶端所需的內容。

\\

使用GraphQL還會有很多收益,比如,另外一個收益就是與多個服務進行通訊的時候。如果你有多個客戶端要從多個服務請求資料的話,位於中間的GraphQL層能夠簡化和標準化這種通訊。儘管這並不是針對REST API的(因為它也能很容易地實現),但是GraphQL執行時提供了一個結構化和標準化的方式來實現這一點。

\\

ca615212b4b34a176a32156fd86136e6.jpg

\\

圖片來源於作者Pluralsight課程的截圖:使用GraphQL構建可擴充套件的API

\\

客戶端不會與兩個不同的資料服務直接互動,我們現在可以讓客戶端與GraphQL層進行通訊。然後,GraphQL層會與兩個不同的資料服務進行通訊。這樣的話,GraphQL首先能夠將客戶端進行隔離,這樣它們就沒有必要使用多種語言進行通訊了,同時,GraphQL還會將一個請求轉換為針對不同服務的多個請求,這些不同的服務可能會使用不同的語言編寫。

\\
\

讓我們假設有三個不同的人,他們使用不同的語言並且具備不同型別的知識。假設你有一個問題,該問題需要組合這三個人的知識才能給出答案。如果你有一個能夠說這三門語言的翻譯器,那麼為你的問題給出答案就會變得很容易。這其實就是GraphQL執行時所做的事情。

\
\\

計算機還沒有足夠智慧來回答任意的問題(至少目前還不可以),所以它們必須要在某些地方遵循一定的演算法。這也是我們需要在GraphQL執行時上定義模式的原因,客戶端會使用該模式。

\\

基本上來講,模式就是一個能力文件,它包含了客戶端可以請求GraphQL層的所有問題的列表。在如何使用模式方面有一定的靈活性,因為我們在這裡所討論的是一個節點圖。模式主要體現的是GraphQL層所能回答的問題都有哪些限制。

\\

還感到不清楚嗎?那我們一針見血地回答GraphQL是什麼:REST API的替代品。那麼接下來,我們來回答你最可能會提出的一個問題。

\\

那REST API有什麼問題呢?

\\

REST API最大的問題在於其多端點的特質。這需要客戶端進行多輪請求才能獲取到想要的資料。

\\

REST API通常是端點的集合,其中每個端點代表了一個資源。所以,當客戶端需要來自多個資源的資料時,就需要針對REST API發起多輪請求,這樣才能將客戶端所需的資料組合完整。

\\

在REST API中,沒有客戶端請求語言。客戶端對服務端返回的資料沒有控制權。在這方面,沒有語言能夠幫助它們實現這一點。更精確地說,客戶端可用的語言非常有限。

\\

例如,用來實現讀取(READ)的REST API一般不外乎如下兩種形式:

\\
  • GET /ResouceName:獲取指定資源的所有記錄的列表,或者\\t
  • GET /ResourceName/ResourceID:根據ID獲取單條記錄。\

舉例來說,客戶端無法指定該選擇記錄中的哪個欄位。這些資訊位於REST API服務本身之中,不管客戶端實際需要哪些欄位,REST API服務始終都會返回所有的欄位。GraphQL對該問題的描述術語是過度載入(over-fetching)不需要的資訊。不管是對於客戶端還是對於伺服器端,這都是網路和記憶體資源的一種浪費。

\\

REST API的另外一個大問題是版本化。如果你需要支援多版本的話,通常意味著要有多個端點。在使用和維護這些端點的時候,這通常會導致更多的問題,而這也可能是服務端出現程式碼重複的原因所在。

\\

上文所述的REST API的問題恰好是GraphQL所要致力解決的。上面所述的這些肯定不是REST API的所有問題,我也不想過多討論REST API是什麼,不是什麼。我主要講的是基於資源的HTTP端點API。這些API最終都會變成常規REST端點和自定義專門端點的混合品,其中自定義的專門端點大多都是因為效能的原因而製作的。在這種情況下,GraphQL能夠提供好得多的方案。

\\

GraphQL的魔力是如何實現的呢?

\\

在GraphQL背後有著很多理念和設計決策,但是最為重要的包括:

\\
  • GraphQL模式是強型別的模式。要建立GraphQL模式,我們需要按照型別來定義欄位。這些型別可以是原始型別,也可以是自定義型別,模式中的任何內容都需要一個型別。這種豐富的型別系統允許實現豐富的特性,比如具備內省功能的API,以及為客戶端和服務端構建強大的工具;\\t
  • GraphQL將資料以Graph的形式來進行表示,而資料很自然的表現形式就是圖。如果想要表示任意的資料,那正確的結構就是圖。GraphQL執行時允許我們以圖API的方式來表示資料,該API能夠匹配資料的自然圖形形狀。\\t
  • GraphQL具有一個宣告式的特質來表示資料需求。GraphQL為客戶端提供了一種宣告式的語言,允許它們描述其資料需求。這種宣告式的特質圍繞GraphQL語言建立了心智模型,這與我們使用自然語言思考資料需求的方式非常接近,從而使得GraphQL API要比其他替代方案容易得多。\

其中,正是由於最後一項理念,我個人認為GraphQL將是一個遊戲規則的改變者。

\\

這些都是高層級的理念,接下來讓我們看一些細節。

\\

為了解決多輪網路呼叫的問題,GraphQL將響應伺服器變成了只有一個端點。從根本上來講,GraphQL將自定義端點的思想發揮到了極致,將整個伺服器變成了一個自定義的端點,使其能夠應對所有的資料請求。

\\

與這個單端點概念相關的另一個重要理念是富客戶端請求語言(rich client request language),這是使用自定義端點所需要的。如果沒有客戶端請求語言的話,單端點是沒有什麼用處的。它需要有一種語言來處理自定義的請求併為該請求響應資料。

\\

具備客戶端請求語言就意味著客戶端將會是可控的。客戶端能夠確切地請求它們想要的內容,伺服器端則能夠確切地給出客戶端想要的東西。這解決了過度載入的問題。

\\

在版本化方面,GraphQL有一種非常有趣的做法,能夠徹底避免版本化的問題。從根本上來講,我們可以新增新的欄位,而不必移除舊的欄位,因為我們有一個圖,從而可以通過新增節點來靈活地擴充套件這個圖。所以,我們可以繼續保留舊API的路徑,並引入新的API,而不必將其標記為新版本。API只是不斷增長而已。

\\

這對於移動端尤為重要,因為我們無法控制它們使用哪個版本的API。一旦安裝之後,移動應用可能會多年一直使用相同版本的舊API。在Web端,我們能夠很容易地控制API的版本,我們只需推送並使用新的程式碼即可。對移動應用來說,這樣做就有些困難了。

\\

還不完全相信嗎?我們通過一個具體的例子來對GraphQL和REST進行一對一的比較如何?

\\

RESTful API與GraphQL API的樣例

\\

假設我們是開發人員,負責構建一個嶄新的使用者介面,展現《星球大戰》電影及其角色。

\\

我們要構建的第一個UI介面很簡單:顯示每個《星球大戰》人物資訊的檢視。例如,Darth Vader以及他在哪些電影中出現過。這個檢視將會展現人物的姓名、出生年份、星球名稱以及他們所出現的電影的名字。

\\

聽起來非常簡單,但實際上我們在處理三種不同型別的資源:人物(Person)、星球(Planet)以及電影(Film)。這些資源之間的關係很簡單,任何人都可以猜測到資料的形狀。每個Person物件屬於一個Planet物件,同時每個Person物件有一個或多個Film物件。

\\

這個UI的JSON資料可能會如下所示:

\\
\{\  \"data\": {\    \"person\": {\      \"name\": \"Darth Vader\

相關文章