新一代數據查詢語言GraphQL來啦!
- 2019 年 12 月 5 日
- 筆記
本文作者:IMWeb 孫世吉 原文出處:IMWeb社區 未經同意,禁止轉載
1. GraphQL來啦!
當Facebook構建移動應用的時候,它需要的是一個強大的數據獲取API:
- 足夠強大,滿足Facebook自身複雜業務的需求;
- 足夠簡單,對開發者和使用者來說很容易上手與使用;

GraphQL就是為了滿足這一個需求而產生的,Facebook從2012年開始完善,與2015年展開GraphQL的開源的進程,並形成一個圍繞GraphQL的社區。
GraphQL每天都為Fackbook接收、處理著上百億的請求,為Fackbook提供了強大的基礎數據平台的支援,滋養大量業務與產品。
2. 為什麼是GraphQL?
回到2012年,Fackbook開始重構他們的本地移動應用。當時他們的iOS和android移動應用實際上是就是他們Web應用的內容再加上一個本地瀏覽器的殼。這看上去給他們帶來了「一次開發,多端應用」的好處,但是隨著Fackbook移動應用越來越複雜,這樣的做法直接帶來了極差的性能和時常發生的程式崩潰,所以他們開始開發真正的本地應用。
而當時 Facebook 現有的伺服器主要功能還是只提供 HTML ,數據介面並不能直接復用,服務模式就是請求一個 URL ,返回一堆 HTML。而本地移動應用,為了給應用提供需要的數據,填充數據模型,顯示視圖,要解決的問題是怎麼去請求,準備,傳遞這些數據。
Facebook考量了兩種實現方案,包括RESTful服務資源
和FQL表
。
- RESTful:對於Facebook這種複雜的應用,可能需要定義很多端點,這些數據介面可能只是返回欄位有所不同,造成重複工作,同時難以表達複雜的邏輯;
- FQL:FQL是Facebook類似於SQL的API,它功能強大、格式明確,但是查詢的語言非常難以理解,例如一些數據表JOIN等操作。
Facebook工程師們希望能夠在移動應用和服務端的查詢達到一致,最後使用的模型可以類似於NSObjects或者JSON那樣的結構。
幾個工程師開始了現在的 GraphQL,一種用對象,屬性來表示數據關係,有點像圖形的方式來表達想要的數據。所以最終GraphQL給了產品設計人員和開發人員重新思考移動應用數據獲取的機會,它將開發的重點轉移到了客戶端應用中,這裡是設計師和開發人員最關注的地方。
3. 什麼是GraphQL?
GraphQL是一種API查詢語言,是一個對自定義類型系統執行查詢的服務端運行環境
一個GraphQL查詢是一個被發往服務端的字元串,該查詢在服務端被解釋和執行後返回JSON數據給客戶端。
3.1 定義數據模型
首先讓人一目了然的是GraphQL查詢可以直接映射到返回的數據,它們的結構非常相似。這帶來個好處就是你很簡單就從查詢預測到即將返回的數據,相反的知道需要的數據也可以很簡單寫出相應的查詢語句。更重要的是,這使得GraphQL更加容易學習和應用。
// 下面是一個簡單的GraphQL查詢,獲取id為1001的用戶的名字和頭像 { user (id: 1001){ name, photo } }
// 對應的結果 { "user": { "name": "shiji", "photo": "https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2639867671,3554518423&fm=58" } }
3.2 分層的
GraphQL另外一個重要的方面就是它自然而成的分層結構。GraphQL很自然的以對象和屬性來表示數據之間的關係,這也是GraphQL的命名由來。 你可以簡單的把GraphQL查詢看成三部分。
- 由{}包裹的
對象
- 對象由
屬性
列表組成 - ()包裹的
查詢條件
// 獲取用戶資訊的同時獲取該位用戶的朋友資訊列表,包括姓名、性別和地址資訊 { user (id: 1001){ // 第一層 name, photo, age, friends { // 第二層 name, sex, addr { // 第三層 country, city } } } }
3.3 強類型
每一級GraphQL查詢都關聯著一個特殊的類型,而每一種類型都描述了一組可用的欄位集合。 GraphQL服務通過定義類型和屬性來創建,然後為在這些類型上的每個屬性創建函數。跟SQL類似,這使得GraphQL在執行查詢之前可以提供描述性的錯誤資訊。
// 對應上一個GraphQL查詢,GraphQL 服務需要建立以下自定義類型 type Query { user: User } type User { name: String, photo: String, age: Integer, addr: Address, friends: [User] } type Address { country: String, city: String }
GraphQL 的類型系統分為標量類型(Scalar Types,標量類型)和其他高級數據類型,標量類型即可以表示最細粒度數據結構的數據類型,可以和 JavaScript的原始類型對應。 GraphQL 規範目前規定支援的標量類型有:
Int
: 整數,對應JavaScript的NumberFloat
:浮點數,對應JavaScript的NumberString
:字元串,對應Javascript的StringBoolean
: 布爾值,對應JavaScript的BooleanID
:序列化後唯一的字元串,對應JavaScript的Symbol
高級數據類型包括:Object
、Interface
、Union
、Enum
、Input Object
、List
、Non-Null
這裡不做詳述,請參考官方指引 Schemas and Types。
3.4 協議而非存儲
GraphQL並不直接提供後端存儲的能力,它不綁定任何的資料庫或者存儲引擎,它可以利用你已有的程式碼和技術來進行數據源管理。當然這對改造現有的業務會帶來相應的成本。 也就是說GraphQL提供給你了組織與管理數據源的能力,但是數據具體是存在文件系統還是資料庫它並不關注。
3.5 自檢性
一個GraphQL服務可以直接查詢出它所支援的類型,也就是說你不需要花時間寫API文檔,也不需要花時間理解API。GraphQL直接幫助開發者快速學習和探索API。
// GraphQL查詢 { __schema { queryType { name, fields { name } } } }
// 查詢結果 { "data": { "__schema": { "queryType": { "name": "User", "fields": [{ "name": "name" },{ "name": "photo" },{ "name": "age" },{ "name": "addr" },{ "name": "friends" }] } } } }
每個 GraphQL 根域都會自動加上一個 __schema 域
,這個域有一個子域叫 queryType
。我們可以通過查詢這些域來了解 GraphQL 伺服器支援那些查詢
3.6 無需版本的
返回數據的模型完全由客戶端的查詢決定,所以服務端變得更簡單、更容易一般化。 當你添加新的產品功能時,額外的欄位可以被添加到服務中,同時並不會影響到現有的業務;當你淘汰老功能的時候,遺棄對應的服務欄位依舊可以繼續工作。這種漸進式、向後兼容的過程去除了遞增版本號的需要。在Fackbook中使用相同版本的GraphQL API 支援了跨域三年的Fackbook應用。
4. GraphQL vs RESTful
之前談過Fackbook不使用RESTful自研發GraphQL的原因,這裡再詳細講一下。 RESTful API的問題在於: 1、缺乏可拓展性
。 一個剛開始簡單的用戶介面可能只返回少部分資訊,例如用戶名、頭像等。隨著API的不斷發展,可能需要返回更多的資訊,例如年齡、昵稱、簽名等。很多時候客戶端只是需要其中的部分資訊,但是介面依舊傳輸了所有的資訊,這個情況增加了網路傳輸量,特別對於移動應用來說特別不友好,同時需要客戶端自行提取需要的數據。而建立兩個功能大致相同只是返回欄位有所區別的API則增加了後端實現的複雜度,或者是需要增加業務邏輯判斷,或者是增加了維護的難度。
2、複雜的數據需求需要做多次API調用
。 例如客戶端要顯示文章的內容,可能要調用文章介面、評論介面、用戶資訊介面。為構成對一個資源的完整視圖,需要做多次單獨調用,這樣的數據獲取方式非常不靈活。
而GraphQL給客戶端帶來了自主選擇的權利。
- RESTful:服務端決定有哪些數據獲取方式,客戶端只能挑選使用,如果數據過於冗餘也只能默默接收再對數據進行處理;而數據不能滿足需求則需要請求更多的介面。
- GraphQL:給客戶端自主選擇數據內容的能力,客戶端完全自主決定獲取資訊的內容,服務端負責精確的返回目標數據。
舉個例子:我們要獲取指定id的文章相關資訊,包括標題、作者、發布時間以及前兩條評論;同時載入當前用戶資訊。 RESTful:
// 兩趟查詢,難以拓展 GET /user/111 GET /article/1001?comment=2
GraphQL:
// 一趟查詢,易於擴展 { article (id: 1001){ title, author, time, comments (first: 2) nickname, time, content } }, user (id: 111){ nickname, photo, sign } }
獲取的數據結果如下所示,就是這麼簡單粗暴的完成了API訂製。 我們不僅按照我們的需求完成數據過濾,同時僅僅使用一次請求就獲取了所有你想要的數據。
{ "article": { "title": "新一代API查詢語言GraphQL", "author": "shiji", "time": 1481127981218, "comments": [{ "nickname": "狗剩子", "time": 1481127981218, "content": "樓主寫的真好" }, { "nickname": "不明真相的吃瓜群眾", "time": 1481127981218, "content": "這瓜真好吃" }] }, "user": { "nickname": "shiji", "photo": "https://ss1.baidu.com/6ONXsjip0QIZ8tyhnq/it/u=2639867671,3554518423&fm=58", "sign": "我的地盤我做主——動感地帶" } }
當然GraphQL給伺服器端程式碼帶來了不公平的額外複雜度和管理,GraphQL非常適合於客戶對底層數據具有複雜規模的需求,客戶端完全可以自主訂製個性化API。
5. GraphQL存在的問題
1、改造成本
,要使用GraphQL對數據源進行管理,相當於要對整個服務端進行一次換血。你需要考慮的不僅僅是需要針對現有數據源建立一套GraphQL的類型系統,同時需要改造服務端暴露數據的方式,這對業務久遠的產品無疑是一場災難,讓人望而卻步。 2、實踐方案
,GraphQL在前端如何直接與視圖層、狀態管理方案結合,目前也只有React/Relay這個官方方案。也就是說,如果你不是使用Node+React這個技術棧,引入GraphQL看上去帶來了額外的成本和風險。 3、查詢性能
,GraphQL查詢的每個欄位如果都有自己的resolve方法,可能導致一次查詢操作對資料庫跑了大量了query,資料庫里一趟select+join就能完成的事情在這裡看來會產生大量的資料庫查詢操作,雖然網路層面的請求數被優化了,但是資料庫查詢可能會成為性能瓶頸。
6. GraphQL安全性?
或許有人有疑問,感覺 GraphQL 把我所擁有的資源全部都暴露了,別人不只一覽全局,能夠了解你所有的數據結構,而且還能一次把所有數據都拉取下來,這也太可怕了! 事實上,GraphQL 提供的資源不一定要和你資料庫一樣,因為它只是扮演中間層的角色,雖然也可能很像。所以,你完全可以控制你期望暴露給用戶的資源。

7. 推薦閱讀
教程: 官方教程
簡單應用: 官方Quick Start 搭建一個簡單的GraphQL服務 GitHub GraphQL API
其他: GitHub 16年9月開放新的GraphQL API From RESTful to GraphQL 響應式GraphQL結構