- 原文地址:GraphQL Concepts Visualized
- 原文作者:Dhaivat Pandya
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:Jessica
- 校對者:江五渣,Baddyo
將 GraphQL 概念視覺化
我們將用圖解來視覺化 GraphQL 思維模型
GraphQL 通常被我們解釋為“用於訪問不同來源的資料的統一介面”。雖然這個解釋是對的,但它並沒有揭示 GraphQL 背後的本質和動機,以及它被稱為 “GraphQL” 的原因 —— 這就像,你看到了星星和夜空,並不等於你看到了“星月夜”(畫家梵·高的代表作之一)。
我認為 GraphQL 真正的核心在於應用資料圖。在本文中,我將介紹應用資料圖,討論 GraphQL 如何在應用資料圖上進行查詢操作,以及如何利用 GraphQL 查詢的樹形結構來快取其查詢結果。
更新於 2/7/2017:現在你可以在下面這個視訊裡看到這篇文章的內容了(譯者注:視訊源自 Youtube 需要翻牆)
GraphQL 的思維模型 — Dhaivat Pandya
應用資料圖
現代應用中的很多資料都可以使用帶有節點和邊的圖來表示,其中節點表示物件,邊表示物件之間的關係。
例如,我們正在為圖書館建立一個圖書目錄系統。簡單來說,我們的目錄中有一堆書和作者,每本書至少有一個作者。除此之外,還有合作作者,作者與其他人共同合作寫了一本書或多本書。
如果我們以圖的形式視覺化這些關係,它們看起來是這樣的:
該圖表示了我們的各種資料片段與我們試圖表示的實體(例如 Book 和 Author)之間的關係。幾乎所有的應用都是在這種圖上執行的:它們從圖中讀取資料並對其進行寫操作。這個圖就是 GraphQL 的用武之地。
在 GraphQL 中,我們可以從應用資料圖中提取出一棵樹。
聽到這你可能很會感到疑惑,但讓我們來解釋一下它的意思。根本上來說,樹是一個有起點(根)和屬性的圖。其中它的屬性是不能用手指沿著節點的邊追溯,然後回到同一個節點的,也就是說,它沒有迴圈。
使用 GraphQL 遍歷應用資料圖
讓我們來看一個 GraphQL 查詢示例,從而理解它是如何從應用資料圖中“提取樹”的。這是對應我們上面那張圖書目錄系統應用資料圖的 GraphQL 查詢程式碼,如下所示:
query {
book(isbn: "9780674430006") {
title
authors {
name
}
}
}
複製程式碼
一旦伺服器解析了查詢,它將返回此查詢結果:
{
book: {
title: “Capital in the Twenty First Century”,
authors: [
{ name: ‘Thomas Piketty’ },
{ name: ‘Arthur Goldhammer’ },
]
}
}
複製程式碼
這是用應用資料圖表示的樣子:
查詢路徑
讓我們來看看這些資料是如何通過 GraphQL 查詢從圖中提取出來的。
在 GraphQL 中,我們可以定義根查詢型別(我們將其稱為 RootQuery),該型別定義了遍歷應用資料圖時 GraphQL 查詢應在何處開始。在我們的例子中,我們從一個 “Book” 節點開始,該節點是使用其 ISBN 號也就是查詢欄位 “book(isbn: …)” 選擇的。然後,GraphQL 查詢通過跟蹤每個巢狀欄位標記的邊來遍歷圖。在我們的查詢中,它通過查詢中的 “title” 欄位從 “Book” 節點跳到包含書的標題字串的節點。它還通過跟蹤 “Book” 節點上標有 “authors” 欄位的邊獲取 Author 節點,並獲取每個作者的“name”。
要檢視這個結果如何構造出一棵樹,只需移動節點使其看起來更像一棵樹:
對於查詢返回的每條資訊,有一個與之關聯的查詢路徑。該路徑由 GraphQL 查詢中的欄位組成,我們按照該欄位來獲取該資料。例如,圖書的標題 “Capital” 的查詢路徑如下:
RootQuery → book(isbn: “9780674430006”) → title
我們 GraphQL 查詢中的欄位(即 book、authors、name)指定了應用資料圖中應該選擇哪些邊來獲得我們想要的結果。這就是 GraphQL 名稱的由來:GraphQL 是一種查詢語言,它遍歷資料圖以生成查詢結果樹。
快取 GraphQL 結果
要構建一個真正快速、流暢的應用,使使用者不會浪費大量時間在等待載入的載入動畫上,我們希望使用快取來減少客戶端與伺服器之間的請求。事實證明,GraphQL 的樹形結構非常適合客戶端快取。
舉一個簡單的例子,假設您的頁面上有一些程式碼可以獲取到以下 GraphQL 查詢結果:
query {
author(id: "8") {
name
}
}
複製程式碼
稍後,頁面的其他部分將再次請求這個相同的查詢。除非我們完全需要最新的資料,否則可以使用我們已有的資料來響應第二個查詢!這意味著快取需要能夠在不將查詢傳送到伺服器的情況下解決查詢,從而使我們的應用執行得更快。但是,僅僅是快取我們之前獲取的準確查詢還不夠,我們還可以做得更好。
讓我們來看看 Apollo Client 快取 GraphQL 結果的方法。從根本上講,GraphQL 查詢結果是從伺服器端資料圖中形成的資訊樹。為了避免每次我們再次需要它們時都重新載入它們,我們希望能夠快取這些結果樹。為此,我們先做出一個關鍵假設:
Apollo Client 假設應用資料圖中的每個路徑(由 GraphQL 查詢指定)都指向一個穩定的資訊塊。
如果在某些不成立的情況下(例如,當特定查詢路徑指向的資訊非常頻繁地更改時),我們可以用物件識別符號的概念來防止 Apollo Client 做出這樣的假設,稍後我們將介紹這個概念。但是,一般來說,當涉及到快取時,這是一個合理的假設。
路徑相同,物件相同
這個最後介紹的“路徑相同,物件相同”假設是極其有用的。例如,假設我們有兩個查詢,一個接一個地觸發:
query particularAuthor {
author(name: "Thomas Piketty") {
name
age
}
}
query authorAndBook {
book(isbn: "9780674430006") {
title
}
author(name: "Thomas Piketty") {
name
age
}
}
複製程式碼
只需檢視查詢就可以看到,第二個查詢不需要到伺服器獲取作者的姓名。此資訊可以在快取中從上一個查詢的結果中找到。
Apollo Client 使用這種邏輯來根據快取中已有的資料去掉部分查詢。它能支援這種對比查詢是因為路徑假設。它假設路徑 RootQuery→author(id: 6)→name 在兩個查詢中獲取了相同的資訊。當然,如果您不希望使用這個假設,您可以使用 forceFetch 選項,快取將被完全覆蓋。
這個假設非常有用,因為查詢路徑中還包括我們在 GraphQL 中使用的引數。例如:
RootQuery → author(id: 3) → name
就不等同於
RootQuery → author(id: 6) → name
因此 Apollo Client 不會假設它們代表相同的資訊,並嘗試將其中一個與另一個的結果合併。
當路徑假設不夠時,使用物件識別符號
事實證明,除了從根開始跟蹤查詢路徑以外,我們還可以做得更好。有時,您可能通過兩個完全不同的查詢訪問到同一個物件,從而為該物件提供兩個不同的查詢路徑。
例如,假設我們的每個作者都有一些共同作者,那麼我們最終可以通過該欄位訪問一些 “Author” 物件:
query {
author(name: "Arthur Goldhammer") {
coauthors {
name
id
}
}
}
複製程式碼
但我們也可以直接從根節點訪問到一個作者:
query {
author(id: "5") {
name
id
}
}
複製程式碼
假設名為 “Arthur Goldhammer” 的作者和 ID 為 5 的作者是某本書的合作作者。然後,我們將在快取中兩次儲存相同的資訊(即關於 ID 為 5 的作者,Thomas Piketty 的資訊)。
那麼,快取中的樹形快取結構就像是這樣:
現在的問題是,這兩個查詢都引用了應用程式資料圖中的同一條資訊,但是 Apollo Client 還並不知道這件事。為了解決該問題,Apollo Client 提出了第二個概念:物件識別符號。基本上,您可以為查詢的任何物件指定惟一識別符號。然後,Apollo Client 會認為所有具有相同物件識別符號的物件表示相同的資訊。
一旦 Apollo Client 知道了這一點,它就可以以更好的方式重新安排快取:
這意味著物件識別符號在整個應用中必須是唯一的。因此,您不能直接使用 SQL ID,因為這樣一來,作者的 SQL ID 可能是 5,圖書的 SQL ID 也可能是 5。但這很容易解決:要生成唯一的物件識別符號,只需將 GraphQL 返回的 __typename 附加到後端生成的 ID 即可。因此,一個 SQL ID 為 5 的作者可以有一個 Author:5 或類似的物件識別符號。
保持查詢結果一致
繼續我們剛才處理的最後兩個查詢,讓我們考慮一下如果某些資料發生更改會發生什麼。例如,如果您獲取其他查詢時發現 ID 為 5 的作者更改了姓名,該怎麼辦?同時,這個 ID 為 5 的作者使用的舊名稱的 UI 部分會發生什麼情況?
重頭戲來了:它們會自動更新。這就引出了 Apollo Client 提供的另一個功能:如果所觀察的查詢樹的任何節點的值發生變化,查詢將使用新的結果進行更新。
因此,在本例中,我們有兩個查詢都依賴於作者,其物件識別符號為“Author:5”。由於這兩個查詢樹都引用了作者這個屬性,所以對作者資訊的任何更新都將傳播到這兩個查詢:
如果您在 Apollo Client 中使用 react-apollo 或 angular2-apollo 這樣的檢視整合包,就無需為此進行設定:您的元件將直接獲得新資料並自動重新渲染。如果您沒有使用檢視整合包,那麼核心方法 watchQuery 也可以做到,它為您提供一個觀察者物件,每當儲存更改時它將進行更新。
有時,對於您的應用來說,在所有內容上都使用物件識別符號是不合理的,或者您可能不想直接在程式碼中處理它們,但仍需要更新快取中的特定資訊。我們提供方便而強大的 API 可以解決這個問題,例如 updateQueries 或 fetchMore,可以使您通過非常精細的控制將新資訊合併到這些查詢樹中。
總結
任何應用程式的主幹部分都位於應用資料圖中。在過去,當我們必須將自己的 HTTP 請求送到 REST 端點以將資料匯入和匯出到應用程式資料圖時,在客戶端上進行快取是非常困難的,因為資料獲取是特定於客戶端應用的。而現在,GraphQL 為我們提供了大量資訊,我們還可以利用這些資訊自動快取資料。
如果你理解了這 5 個簡單的概念,您就能理解在 Apollo Client 中的反應和快取(即所有使您的應用快速而流暢的魔術)是如何工作的。這裡,我們再重複一遍:
- GraphQL 查詢意味著從應用資料圖中獲取樹的方法。我們將這些樹稱為查詢結果樹。
- Apollo Client 快取了查詢結果樹。為此,它應用了兩個假設:
- 路徑相同,物件相同 —— 相同的查詢路徑通常指向相同的資訊。
- 當路徑假設不夠時,使用物件識別符號 —— 如果兩個查詢結果被賦予相同的物件識別符號,則它們表示相同的節點或者資訊。
- 如果查詢結果樹中的任何快取節點被更新,Apollo Client 將使用新結果更新查詢。
一般來說,瞭解以上的內容,就足夠使您成為 Apollo Client 和 GraphQL 快取方面的專家了。覺得這篇文章資訊量太大了?別擔心 —— 如果可以的話,我們將繼續釋出更多類似這樣的概念資訊,以便每個人都能理解 GraphQL 背後的目的、它的名稱的由來,以及如何清楚地解釋 GraphQL 結果快取的各個方面。
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。