[譯] 將 GraphQL 概念視覺化

JessicaC發表於2019-12-02

將 GraphQL 概念視覺化

我們將用圖解來視覺化 GraphQL 思維模型

GraphQL 通常被我們解釋為“用於訪問不同來源的資料的統一介面”。雖然這個解釋是對的,但它並沒有揭示 GraphQL 背後的本質和動機,以及它被稱為 “GraphQL” 的原因 —— 這就像,你看到了星星和夜空,並不等於你看到了“星月夜”(畫家梵·高的代表作之一)。

我認為 GraphQL 真正的核心在於應用資料圖。在本文中,我將介紹應用資料圖,討論 GraphQL 如何在應用資料圖上進行查詢操作,以及如何利用 GraphQL 查詢的樹形結構來快取其查詢結果。

更新於 2/7/2017:現在你可以在下面這個視訊裡看到這篇文章的內容了(譯者注:視訊源自 Youtube 需要翻牆)

GraphQL 的思維模型 — Dhaivat Pandya

應用資料圖

現代應用中的很多資料都可以使用帶有節點和邊的圖來表示,其中節點表示物件,邊表示物件之間的關係。

例如,我們正在為圖書館建立一個圖書目錄系統。簡單來說,我們的目錄中有一堆書和作者,每本書至少有一個作者。除此之外,還有合作作者,作者與其他人共同合作寫了一本書或多本書。

如果我們以圖的形式視覺化這些關係,它們看起來是這樣的:

[譯] 將 GraphQL 概念視覺化

該圖表示了我們的各種資料片段與我們試圖表示的實體(例如 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 查詢從圖中提取出來的。

在 GraphQL 中,我們可以定義根查詢型別(我們將其稱為 RootQuery),該型別定義了遍歷應用資料圖時 GraphQL 查詢應在何處開始。在我們的例子中,我們從一個 “Book” 節點開始,該節點是使用其 ISBN 號也就是查詢欄位 “book(isbn: …)” 選擇的。然後,GraphQL 查詢通過跟蹤每個巢狀欄位標記的邊來遍歷圖。在我們的查詢中,它通過查詢中的 “title” 欄位從 “Book” 節點跳到包含書的標題字串的節點。它還通過跟蹤 “Book” 節點上標有 “authors” 欄位的邊獲取 Author 節點,並獲取每個作者的“name”。

要檢視這個結果如何構造出一棵樹,只需移動節點使其看起來更像一棵樹:

[譯] 將 GraphQL 概念視覺化

對於查詢返回的每條資訊,有一個與之關聯的查詢路徑。該路徑由 GraphQL 查詢中的欄位組成,我們按照該欄位來獲取該資料。例如,圖書的標題 “Capital” 的查詢路徑如下:

RootQuery → book(isbn: “9780674430006”) → title

我們 GraphQL 查詢中的欄位(即 bookauthorsname)指定了應用資料圖中應該選擇哪些邊來獲得我們想要的結果。這就是 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 使用這種邏輯來根據快取中已有的資料去掉部分查詢。它能支援這種對比查詢是因為路徑假設。它假設路徑 RootQueryauthor(id: 6)name 在兩個查詢中獲取了相同的資訊。當然,如果您不希望使用這個假設,您可以使用 forceFetch 選項,快取將被完全覆蓋。

[譯] 將 GraphQL 概念視覺化

這個假設非常有用,因為查詢路徑中還包括我們在 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 的資訊)。

那麼,快取中的樹形快取結構就像是這樣:

[譯] 將 GraphQL 概念視覺化

現在的問題是,這兩個查詢都引用了應用程式資料圖中的同一條資訊,但是 Apollo Client 還並不知道這件事。為了解決該問題,Apollo Client 提出了第二個概念:物件識別符號。基本上,您可以為查詢的任何物件指定惟一識別符號。然後,Apollo Client 會認為所有具有相同物件識別符號的物件表示相同的資訊

一旦 Apollo Client 知道了這一點,它就可以以更好的方式重新安排快取:

[譯] 將 GraphQL 概念視覺化

這意味著物件識別符號在整個應用中必須是唯一的。因此,您不能直接使用 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”。由於這兩個查詢樹都引用了作者這個屬性,所以對作者資訊的任何更新都將傳播到這兩個查詢:

[譯] 將 GraphQL 概念視覺化

如果您在 Apollo Client 中使用 react-apolloangular2-apollo 這樣的檢視整合包,就無需為此進行設定:您的元件將直接獲得新資料並自動重新渲染。如果您沒有使用檢視整合包,那麼核心方法 watchQuery 也可以做到,它為您提供一個觀察者物件,每當儲存更改時它將進行更新。

有時,對於您的應用來說,在所有內容上都使用物件識別符號是不合理的,或者您可能不想直接在程式碼中處理它們,但仍需要更新快取中的特定資訊。我們提供方便而強大的 API 可以解決這個問題,例如 updateQueriesfetchMore,可以使您通過非常精細的控制將新資訊合併到這些查詢樹中。

總結

任何應用程式的主幹部分都位於應用資料圖中。在過去,當我們必須將自己的 HTTP 請求送到 REST 端點以將資料匯入和匯出到應用程式資料圖時,在客戶端上進行快取是非常困難的,因為資料獲取是特定於客戶端應用的。而現在,GraphQL 為我們提供了大量資訊,我們還可以利用這些資訊自動快取資料。

如果你理解了這 5 個簡單的概念,您就能理解在 Apollo Client 中的反應和快取(即所有使您的應用快速而流暢的魔術)是如何工作的。這裡,我們再重複一遍:

  1. GraphQL 查詢意味著從應用資料圖中獲取樹的方法。我們將這些樹稱為查詢結果樹。
  2. Apollo Client 快取了查詢結果樹。為此,它應用了兩個假設:
  3. 路徑相同,物件相同 —— 相同的查詢路徑通常指向相同的資訊。
  4. 當路徑假設不夠時,使用物件識別符號 —— 如果兩個查詢結果被賦予相同的物件識別符號,則它們表示相同的節點或者資訊。
  5. 如果查詢結果樹中的任何快取節點被更新,Apollo Client 將使用新結果更新查詢。

一般來說,瞭解以上的內容,就足夠使您成為 Apollo Client 和 GraphQL 快取方面的專家了。覺得這篇文章資訊量太大了?別擔心 —— 如果可以的話,我們將繼續釋出更多類似這樣的概念資訊,以便每個人都能理解 GraphQL 背後的目的、它的名稱的由來,以及如何清楚地解釋 GraphQL 結果快取的各個方面。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章