[譯] 為什么選用 GraphQL:它的優點、缺點和備選方案

知乎連接

本文翻譯自《React 學習之道》 原作者 Robin 的一片文章 https://www.robinwieruch.de/why-graphql-advantages-disadvantages-alternatives/

這篇包含兩個部分,本文是它的第一部分。

Part 2:為什么選用 Apollo:它的優點、缺點和備選方案


當談論到客戶端與服務端之間的網絡請求時,REST 絕對是連接兩者的方案選型中最流行的選擇。在 REST 中,所有概念都是在可以通過 URL 可訪問的資源這個概念周圍演化而來的。你可以通過一個 HTTP GET 請求讀取一個資源,通過一個 HTTP POST 請求創建一個資源,或者通過 HTTP PUT 和 HTTP DELETE 更新或刪除一個資源。這些被稱為 CRUD(Create、Read、Update、Delete)操作。資源可以是任何實體,比如作者(authors)、文章(articles)或者用戶(users)。使用 REST 時,傳輸數據的格式并沒有定論,但是多數人會使用 JSON 作為傳輸數據媒介。最終,REST 使應用之間能直接通過原生的 HTTP URL 以及 HTTP 方法進行數據溝通。

// 一個 RESTful 請求
GET https://api.domain.com/authors/7

// JSON 數據的響應
{
  "id": "7",
  "name": "Robin Wieruch",
  "avatarUrl": "https://domain.com/authors/7",
  "firstName": "Robin",
  "lastName": "Wieruch"
}

即便 REST 是現在普遍的選擇,但最近幾年出現了另外一套由 Facebook 提出的技術方案:被稱為 GraphQL。下面會給你一個 GraphQL 的簡單介紹:它的優點和缺點,以及是否有一些備選方案。


什么是 GraphQL?

在我們談論 GraphQL 的優點和缺點前,我們先回答下這個問題:什么是 GraphQL?GraphQL是由 Facebook 在 2012 年創立的一門開源查詢語言。在它開源之前,Facebook 就已經在內部移動端應用中使用過。為什么選用移動應用?GraphQL 作為通用的 REST 架構的替代方案而被開發出來,它允許客戶端只請求其需要的數據——不多也不少,一切在客戶端的主導下。在一個 RESTful 架構下,因為后端開發人員定義在各個 URL 的資源上返回的數據,而不是前端開發人員來提出數據需求,使得按需獲取數據會非常困難。經常前端需要請求一個資源中所有的信息,即便只需要其中的一部分數據。這個問題被稱之為過度獲取(overfetching)。最惡劣的場景下,一個客戶端應用不得不請求多個而不是一個資源,這通常會發起多個網絡請求。這不僅會造成過度獲取的問題,也會造成瀑布式的網絡請求(waterfall network requests)。那么將像 GraphQL 之類的查詢語言,不僅在服務端程使用,也應用到客戶端的話,客戶端來決定需要什么數據,這樣只需要發送一個請求到服務端。在 Facebook 的 GraphQL 移動端開發場景下,這極大地減少了忘了請求,因為 GraphQL 一次只需要發起一個請求,并且傳輸中數據數量也減少了。

Facebook 開源了 GraphQL 標準和其 JavaScript 版本的實現。后來主要編程語言也實現了標準。此外,GraphQL 周邊的生態不僅僅水平上擴展了不同語言的實現,并且還出現了在 GraphQL 基礎上實現了類庫(比如 Apollo 和 Relay)。

一個 GraphQL 操作可以是一個查詢(query(讀操作))、修改(mutation(寫操作))以及訂閱(subscription(持續讀操作))。這些操作中每一種都只是根據 GraphQL 標準構造的一段字符串而已。一旦一個 GraphQL 操作從前端應用到達后端應用,首先會在后端解釋整個 GraphQL schema,然后再為前端解析相關的數據。GraphQL 并沒有要求網絡層選型(通常是 HTTP),也沒有要求傳輸數據格式(通常是 JSON)。甚至沒有要求應用架構(通常是前后端分離架構)。它只是一個查詢語言。

// GraphQL 查詢
author(id: "7") {
  id
  name
  avatarUrl
  articles(limit: 2) {
    name
    urlSlug
  }
}

// GraphQL 查詢結果
{
  "data": {
    "author": {
      "id": "7",
      "name": "Robin Wieruch",
      "avatarUrl": "https://domain.com/authors/7",
      "articles": [
        {
          "name": "The Road to learn React",
          "urlSlug": "the-road-to-learn-react"
        },
        {
          "name": "React Testing Tutorial",
          "urlSlug": "react-testing-tutorial"
        }
      ]
    }
  }
}

如你所見,一個查詢請求了多個資源(作者(author)、文章(article)),在 GraphQL 中被稱為字段(fileds),及時 GraphQL scheme 提供了更多是數據(比如文章中的描述(description)和發布時間(releaseDate) ),我們只會拿到這些字段的一個子集(文章中的名稱(name)和 urlSlug)。相對地,在 RESTful 架構中,需要至少兩個連續請求分別獲取作者實體和它的文章,GraphQL 只需要一個查詢就可以做到。此外,查詢只需要選擇必要的字段即可,而不是整個數據實體。

這就是 GraphQL 的基本介紹。服務端提供了定義了可用數據的層級關系和類型的 GraphQL schema,客戶端只用查詢其需要的數據即可。


GraphQL 的優點

接下來會列舉在應用中使用 GraphQL 的主要優點。

聲明式地數據獲取

如之前看到的那樣,GraphQL 在使用查詢語句式,使用聲明式的方式獲取數據。客戶端在一個查詢請求中,選擇需要的數據和相關的字段實體。客戶端根據其 UI 來決定需要的字段。你可以說這是 UI 驅動地數據獲取。比方說,Airbnb 使用 GraphQL的例子,在 Airbnb 中的一個搜索界面,經常需要搜索房屋的住房體驗和其他相關的一些信息,為了能在一個請求中檢索所有的數據,一個 GraphQL 查詢會根據 UI 選擇數據中的一部分達到完美的匹配。畢竟,GraphQL 提供了極佳的關注點分離方式:客戶端知道它需要什么數據,服務端知道數據的結構,以及如何從一些數據源(比如數據庫、微服務、第三方 API)中拉取數據。

在 GraphQL 中沒有過度獲取

使用 GraphQL 不會存在過度獲取的現象。使用與 Web 客戶端公用的一個 RESTful API,一個移動客戶端很可能會獲取過多的數據,但是使用同樣的 GraphQL API,移動客戶端可以選擇和 Web 客戶端不同的數據字段。因此移動客戶端能減少獲取的信息,因為相對于 Web 應用的更大的屏幕,小屏幕上可能顯示不了那么多信息。GraphQL 通過最開始按客戶端需求選擇數據,減少了傳輸數據的大小。

在 React、Angular、Node 和 Co 中的 GraphQL

GraphQL 并不只讓 React 的開發者激動。即便 Facebook 只展示了一個使用 React 的客戶端程序,但是它和任何前端或者后端的解決方案是解耦的,無關的。GraphQL 的相關實現是用 JavaScript 寫的,因此 GraphQL 可以用在 Angular、Vue、Express、Hapi、Koa 以及任何其他客戶端或者服務端的 JavaScript 類庫上,并且這還只是在 JavaScript 的生態中。GraphQL 模仿了讓 REST 這么流行的一個特點:一個兩個實體(比如服務端和客戶端)語言無關的接口(查詢語言)。這樣你可以在任意編程語言中通過使用一個 GraphQL 標準的實現使用 GraphQL 了。

誰在使用 GraphQL?

自從 2012 年,在 GraphQL 開源前,Facebook 就開始使用 GraphQL了,它是驅動 GraphQL 規范化和 JavaScript 參考實現背后的公司。所以開始使用 GraphQL,你就已經站在他們的肩膀之上了。其他著名的公司也在他們的應用中使用著 GraphQL。因為它們應用中也有著龐大的需求,它們投身于 GraphQL 生態。因此,你不是唯一站在 Facebook 肩膀上的人,此外還有這些公司:

當 GraphQL 被 Facebook 開源和開發出來后,其他公司在他們的應用中遇到類似的問題。這就是為什么 Netflix 會退出 [Falcor](https://github.com/Netflix/falcor),看起來是 GraphQL 的一個備選方案。這僅展示了現代應用需求著如 GraphQL 和 Falcor 這樣的解決方案。

單一數據源(Single Source of Truth)

在 GraphQL 應用中存在者單一數據源:GraphQL schema。它提供了一個所有可用數據檢索的源頭。鑒于 GraphQL 的 schema 通常會在服務端定義,客戶端可以基于 schema 讀取(query)和寫入(mutation)數據。因此,服務端提供了所有可用的信息,客戶端只需要執行 GraphQL 查詢獲取部分數據,或者通過 GraphQL 修改變更部分數據。

GraphQL 擁抱趨勢

GraphQL 適應了現在應用構建的變化趨勢。你可能只有一個后端應用,但是可能會有多個依賴同一個后端應用的客戶端(web 端、移動端、智能手表等等...)。因此 GraphQL 不僅能在前后端進行溝通,也能滿足每一個客戶端的具體要求(比如網絡使用的要求、數據嵌套的要求、按需獲取數據的要求),而不需要為每一個客戶端定制不同的 API。

另外一方面,在服務端,可能不止一個后端應用,而是一個微服務集群來提供各自具體的功能。這簡直是 GraphQL 的完美使用場景,它將所有的功能編織匯總到一個 GraphQL schema 匯總。

拼接 GraphQL Schema

拼接 Schema 使得多個 schema 可以聚合成一個。什么時候你需要考慮這個?考慮一下后端的微服務架構。每個微服務處理特定域的業務邏輯和數據。因此,每個微服務都可以定義自己的GraphQL架構。之后,使用 Schema 拼接將所有 Schema 聚合到一個可以被客戶端訪問的 Schema 中。最終,每個微服務都可以擁有自己的 GraphQL 端點,而一個 GraphQL API網關將所有 schema 合并到一個全局 schema 中,以便使得客戶端可以使用。

GraphQL 自省(Introspection)

GraphQL 自省允許通過 GraphQL API 檢索 GraphQL schema。因為 schema 包含了包含了 GraphQL API 可以獲得的所有數據信息,本身就是一份完美的自動生成的 API 文檔。不僅僅是 API 的文檔,也允許客戶端通過mock GraphQL 的 schema 達到測試的目的,或者使用 schema 拼接的接口檢索多個微服務的 schema。

強類型的 GraphQL

GraphQL 是一門強類型的查詢語言,因為它是通過 GraphQL Schema Definition Language(SDL)書寫的。因為有了強類型,它就擁有了強類型編程語言一樣的好處:更不容出錯、可以在編輯期驗證并且支持編輯器智能補全和驗證相關的集成。

GraphQL 版本化

在 GraphQL 中沒有 API 版本的說法。在 REST 中,通常會提供一個 API 的多個版本(比如 api.domain.com/v1/、api.domain.com/v2/),因為隨著時間過去,可能資源的結構也會發生變化。在 GraphQL 中,API 廢棄可以做到字段級別,因此當一個客戶端減少到一個廢棄的字段,會得到一個廢棄相關的警告。當沒有客戶端再使用這個廢棄知道后,就可以從 schema 匯總移除這個字段了。這讓一個 GraphQL API 不需要使用版本化的方式來演進。

成長中的 GraphQL 生態

GraphQL 的生態正在發展壯大。不僅僅是 GraphQL 天然的強類型特性適宜集成編輯器和 IDE 的演進,GraphQL 相關的應用也在演進。你可能記得在處理 REST API 的時候的 Postman,現在有 GraphiQL 或者 GraphQL Playground可以調試你的應用。你也可以找到如 Gatsby.js 這樣的使用 GraphQL 的 React 靜態頁面生成器。比如,使用 Gatsby.js 你可以在構建時期通過一個 GraphQL 來提供你的博客內容來源。你可能還聽說過內容管理系統(CMS)(比如 GraphCMS)通過 GraphQL API 來提供(博客)內容。不僅在技術領域有所演進,這還有很多 GraphQL 相關 大會、聚會和社區不斷涌現,并且也可以通過一些 newsletters 和 podcast 了解到 GraphQL。

我應該全部投入到 GraphQL 么?

采用 GraphQL 并不需要將現有技術棧全部一步推翻。如果你計劃從一個單體后端應用遷移到一個微服務架構上去,正是一個絕好的時機為新的微服務引入 GraphQL API。當有多個微服務時,你的團隊可以通過 schema 拼接的方式引入一個 GraphQL 網關(gateway)。不過 API 網關并不是微服務中才能使用的方式,單體 REST 應用也可以。你可以通過將所有現有的 API 通過一個 API 網關不斷一步一步匯集到一起,逐步完成到 GraphQL 的遷移。

GraphQL 的缺點

下面的話題展示了使用 GraphQL 的一些不足

GraphQL 查詢的復雜性

人們經常錯誤地認為 GraphQL 就是在后端替代了數據庫。并不是這樣的,GraphQL 僅僅是一個查詢語言。在服務端,一個查詢需要解析數據,因此一個 GraphQL 相關實現常常需要執行數據庫訪問,但 GraphQL 其實不關心這些。還有,GraphQL 在你需要在一個查詢中獲取多個字段(作者、文章、評論)的時候,它對性能瓶頸沒有任何幫助。無論使用 RESTful 架構還是 GraphQL,不同資源/字段仍然需要從一個數據源去獲取。

因此當一個客戶端需要一次查詢很多嵌套字段時,前端開發通常不能很清楚他正在通過服務端訪問不同的數據庫獲取過多的數據。這需要一種機制(比如最深查詢深度、查詢復雜度權重、避免遞歸、持久化查詢)來制止來自客戶端的(性能)昂貴的查詢。

查詢頻率限制

另一個問題是頻率限制,在 REST 中,可以簡單的聲明”一天之中,我們只允許請求這么多資源“,在一個獨立的 GraphQL 操作中很難做到這一點,因為任何操作的開銷都可以是廉價的或者昂貴的。這就是那些有著公共 GraphQL API 的公司提出的特定速率限制計算,通常可以歸結為前面提到的最大查詢深度和查詢復雜度權重問題。

GraphQL 緩存

一個簡單緩存,相比 REST,在 GraphQL 中實現會變得極其復雜。在 REST 中你通過 URL 訪問資源,因此你可以在資源級別實現緩存,因為資源使用 URL 作為其標識符。在 GraphQL 中就復雜了,因為即便它操作的是同一個實體,每個查詢都各不相同。比如,一個查詢中,你可能只會請求一個作者的名字,但是在另外一次查詢中你可能也想知道他的電子郵箱地址。這就需要你有一個更加健全的機制中來確保字段級別的緩存,實現起來并不簡單。不過,多數基于 GraphQL 構建的類庫都提供了開箱即用的緩存機制。

為什么不用 REST 呢?

GraphQL 是通常用來連接客戶端和服務端的 RESTful 架構的替代方案。在前面的內容中,你已經多次聽到 REST 了,那么什么是使用 GraphQL 而不是 RESTful ,顯而易見的好處呢?

因為 REST 提出通過 URL 來標識資源,最終常常會出現低效的連續請求。比方說,最開始你通過 id 來定位一個作者實體,然后你通過作者的 id 獲取的某個信息來請求他所有的文章。在 GraphQL 中只需要一個請求就能辦到,這是更加效率的。更進一步而言,如果你想只想獲取作者的所有文章數據,而不關心作者的信息,GraphQL 允許只你選擇你需要的信息。在 REST 中,你需要先獲取作者的所有實體信息,即使你值關心被這個作者寫的文章而已。因為過度獲取這個問題,只有在使用 REST 才會出現,而 GraphQL 就不會。

現在客戶端應用不適合 RESTful 的服務端應用了。比方說,在 Airbnb 平臺上,獲取搜索結果,它為你展示了房屋、住房體驗以及其他相關信息。住房和住房體驗在各自的 RESTful 資源中,那么你需要支持多個網絡請求。當使用 GraphQL API,你只需要在一個 GraphQL 查詢中一起獲取所有需要的實體(比如住房和住房體驗)或者嵌套的其他相關信息(比如作者的文章信息)。

最終 GraphQL 將數據有服務端主導返回什么數據變成了客戶端決定需要什么什么。這就是一開始 GraphQL 被發明的原因,因為在 Facebook 的一個移動應用和他們的 web 應用需要的數據是不一樣的。

總之,仍然有些場景下使用 REST 來溝通客戶端和服務端是有價值的途徑,通常應用是資源驅動,也不需要像 GraphQL 這些的查詢語言提供的靈活能力。然而,我還是推薦你嘗試使用 GraphQL 來開發你的下一個客戶/服務端架構。

GraphQL 的替代方案

顯然 REST 是最流行的 GraphQL 替代方案。最近這些年里,通常使用 RESTful 架構來溝通客戶端和服務端,比起其他網絡技術,如 RPCSOAP,因為它使用了 HTTP 的原本的特特性,而其他協議(比如 SOAP)試圖在此基礎上構建起自己的解決方案。

由 Netflix 開發的 Falcor 是之前有提到的另外一個替代方案。它在 GraphQL 被 Facebook 創建的同一時間被開發出來。Netflix 遇到了同樣的問題,并且開源了它的解決方案。在 Falcor 周圍并沒有什么關注,可能是因為 GraphQL 變得非常流行的原因吧,但是在 Netflix 的開發者在過去的時間里,用了很大的經歷開發,因此,這個方案值是得深入研究的。

對于采用 GraphQL 而不是實現另外一套 RESTful 架構,已經有很多理由了。它有很多優點,也非常適合現代軟件架構。所以試著學習并且用它構建一些應用吧。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容