《GraphQL 名詞 101:解析 GraphQL 的查詢語法》【譯】

騰訊IVWEB團隊發表於2018-07-25

The Anatomy of a GraphQL Query

GraphQL 日漸成為資料查詢的主流標準之一,整個生態圈也蓬勃發展。本文則由淺入深地詳細介紹基礎的 GraphQL 格式與關鍵字,有助於初學者對於 GraphQL 的使用形成體系認知。

GraphQL 日漸成為資料查詢的主流標準之一。每天都會產生許多圍繞這項技術發展的精彩討論和新工具。GraphQL最棒的特性就是提供了一個豐富語言集來描述獲取資料的API。但是使用者該如何描述這種查詢語言,以及GraphQL這項核心技術本身呢?let's talk!

GraphQL specification解釋了幾乎所有出現在GraphQL查詢語言中的概念,但是這篇文件實在是太長了,所以我準備在這篇部落格中,藉助一些具體的栗子來闡述其中一些最重要的概念,來幫助你成為GraphQL專家!至少在紙上談兵方面 : )

注! 這篇文章可不是GraphQL的入門讀物。首先,你應該通讀concepts on the graphql.org docs,然後通過Learn Apollo tutorial來學習使用GraphQL,最後當你想繼續深入瞭解這項技術時,再回到這裡來吧!

最基本的GraphQL查詢

大家通常會使用“查詢”來稱呼 GraphQL API 服務的一切。但是這樣稱呼會有太多東西混雜在一起了。我們可能會把我們跪求服務端的一系列行為稱為一次查詢、一次修改或者一次訂閱,但我想“請求(request)”這個詞可能更加複合HTTP通訊的概念,下面我們先來定義一些最基礎的概念:

  • GraphQL 請求體: 使用GraphQL語言定義的一個或多個操作或者資料片段,型別是字串。
  • 操作: 可以被GraphQL執行引擎理解的一次查詢、修改或訂閱。

為了搞清楚GraphQL各種基本操作之間的區別,讓我們先來看一個簡單的GraphQL請求體:

GraphQL document

A simple query and its parts.

這個請求體顯示了GraphQL的主要構建塊,它指定了你嘗試獲取的資料。

  • 欄位(Fields):客戶端請求的資料單元,最後作為JSON響應資料中的一個欄位。請注意,它們始終稱為“欄位”,無論它們所在的層次有多深。在你的查詢中,對根節點欄位的處理和最底層欄位應該是一樣的。
  • 引數(Arguments):一組與特定欄位關聯的鍵值對。這些引數會跟它們相關的欄位一起被傳遞到伺服器端執行,並影響伺服器對欄位的處理方式。如上面的示例,引數可以是字面量,接下來還有引數作為變數形式的栗子。請注意,引數可以顯示在任何欄位中,即使是巢狀層次很深的欄位。

為了讓你以非常簡潔的形式定義一個GraphQL查詢,上面的栗子是GraphQL的一種非常簡單的形式。但是在GraphQL操作中三種可選的部分都沒有在上述栗子中使用。如果你不僅僅是用GraphQL執行查詢操作,或是希望傳遞動態變數到GraphQL查詢中,你就需要利用到這些新的GraphQL特性。

這裡恰好有一個包含了所有可選部分的栗子:

A more detailed query and its parts.

A more detailed query and its parts.
  • 操作型別(Operation type):共三種型別:查詢(query)、更新(mutation)、訂閱(subscription)。它描述了你試圖進行何種操作。然而這些看起來意思很接近的操作,GraphQL伺服器處理它們時還是會有一些不同。
  • 操作名稱(Operation name):為了方便除錯和服務端打日誌,最好給你的查詢賦予語義化的命名。這樣,無論你是在網路日誌中或者GraphQL伺服器上發現錯誤,你都可以通過名字很輕鬆的在程式碼庫中定位問題,而不是靠猜測(類似的工具有 Apollo Optics)。可以把操作名稱想象成你最喜歡的程式語言中,一個語義化的函式名。
  • 變數定義(Variable definitions):當客戶端向GraphQL伺服器傳送查詢時,會存在查詢文件不變,當某些欄位會動態變化的情況。這些就是查詢中的變數。因為GraphQL是靜態型別的,它可以實時驗證你是否傳遞了正確的變數。這正是你宣告變數型別時所計劃提供的能力。

變數使用特定的序列化協議(在目前的 GraphQL 服務實現中,通常是使用JSON )通過查詢文件獨立傳輸。下面是一個變數物件在查詢文件中的示例:

An example variables object.

An example variables object.
可以看到,這裡的關鍵是變數名稱需要與變數定義所匹配,其名稱是`Episode`列舉中的一個成員。
  • 變數(Variables): 它是傳遞給GraphQL operation的值的字典,提供了operation的動態入參。

這裡有一個在談及Graph的技術意義時很重要,卻不常被提及的核心概念——花括號之間的所有東西叫什麼?

《GraphQL 名詞 101:解析 GraphQL 的查詢語法》【譯】
*選擇集(selection set)*是一個會在GraphQL 文件中經常出現的概念,它賦予了GraphQL遞迴的特性,允許你獲取巢狀形式的資料。

  • 選擇集(selection set):它是一次operation中需要的一組欄位,或者被巢狀在其他的欄位中。GraphQL查詢必須包含一個標識選擇集的欄位,且該欄位返回的是物件型別,選擇集不能設定在返回值是標量型別(Scalar Types)的欄位上,例如Int或者String

片段(Fragments)

當開始介紹片段(fragments)之後,GraphQL 將變得更加強大。它帶來了一系列新的概念。

  • 片段定義(Fragment definition):定義一個片段是GraphQL文件的一部分。為了區別於我們下面會介紹的內聯片段,它有時候也被稱為片段命名

Fragments

A fragment definition and its parts.
  • **片段名稱(Fragment name): **片段(fragments )名在GraphQL文件中必須是唯一的。這個名稱用於在操作(operations)或者其他片段(fragments )引用片段(fragments )。就像操作(operations)名稱一樣,片段名也能用於服務端日誌除錯,所以我們推薦使用明確且有意義的片段(fragments )名。如果你使用了正確的片段(fragments )名,在優化資料獲取時,你能夠很好的追蹤你的程式碼。
  • 型別條件(Type condition): GraphQL操作總是開始於查詢、修改或者訂閱schema中的型別,但是片段(fragments )能夠用於任一選擇,所以為了將校驗片段(fragments )與校驗schema獨立開,你需要指定片段(fragments )能夠使用的型別,而這就是型別條件(Type condition)的作用。

就像操作(operations)一樣,片段也選擇集,使用起來也跟在操作(operations)中使用選擇集一樣。

在你的操作(operations)中使用片段(fragments )

片段(fragments )只有在操作(operations)中使用才能發揮出作用。片段是 GraphQL 的主要組合資料結構,通過片段可以重用重複的欄位選擇,減少 query 中的重複內容。接下來我們將介紹使用片段(fragments )的兩種方式:

fragments

  • **片段擴充套件運算子(Fragment spread): ** 當你在操作或者其他片段中使用片段時,你可以將片段名置於...之後來表示片段。例如沒有片段時需要這樣編寫 query:
query noFragments {
  user(id: 4) {
    friends(first: 10) {
      id
      name
      profilePic(size: 50)
    }
    mutualFriends(first: 10) {
      id
      name
      profilePic(size: 50)
    }
  }
}
複製程式碼

query 中存在下列重複的選擇集合:

{
  id
  name
  profilePic(size: 50)
}
複製程式碼

可以用片段簡化為:

query withFragments {
  user(id: 4) {
    friends(first: 10) {
      ...friendFields
    }
    mutualFriends(first: 10) {
      ...friendFields
    }
  }
}

fragment friendFields on User {
  id
  name
  profilePic(size: 50)
}
複製程式碼

使用片段時需要加上 ... 操作符表示展開片段內容,這稱為片段擴充套件運算子(fragment spread),它可以用在任何選擇集(selection set)中,用以匹配片段的型別條件。

  • 內聯片段(Inline fragment): 如果你僅僅是想執行一些依賴結果型別的欄位,卻不想把它們抽離成獨立的定義,你可以使用內聯片段( inline fragment)。它使用起來跟獨立的命名片段一樣,但是寫在查詢的內部。有一點不同的是,對於內聯片段來說型別條件(type condition)不是必須的,可以像使用指令一樣來使用它,接下來我們會演示指令(directive)的栗子。

指令(Directives)

指令是獨立於GraphQL server之外的一個附加功能。指令不會對結果的值產生影響,但是會影響哪些結果會被返回,也許還會影響這些結果是如何被執行的。指令可以出現在查詢的任何地方,但在這篇文章中我們只關注當前GrahpQL文件所描述的skip(忽略)include(包括) 兩個指令。

Directives

You probably wouldn’t usually put all of these in one query, but it’s the easiest way to demonstrate.
你能在上文廚房水槽的栗子中使用指令`skip` 和 `include`。`include` 指令表示只有在 if 引數為 true 時才引入片段表示的欄位。`skip` 指令表示在 if 引數為 true 時忽略片段中的欄位。由於指令的語法相當靈活,我們可以利用它來給GraphQL新增更多的特性,而不是使用語法解析或者引入更復雜的工具的方式。
  • **指令(Directive): ** 在欄位、片段或者查詢中的一個註釋,include 指令表示只有在 if 引數為 true 時才引入片段表示的欄位。skip 指令表示在 if 引數為 true 時忽略片段中的欄位。
  • 指令引數(Directive arguments): 與欄位引數類似,只不過它們是被執行引擎處理,而不是傳遞給欄位解析器(field resolver)。

總結

GraphQL 是在應用層對業務資料模型的抽象,是對資料請求定製的 DSQL,它解除了介面和資料之間的繫結,對業務資料結構做了抽象和整理,業務邏輯中的資料依賴於底層資料庫結構,並且可以由具體業務場景來定製,不同的業務場景只要基於同樣一套基礎業務資料模型就可以得到複用,在我看來,這才是 GraphQL 帶來的最大改變和收益。 目前GitHub整站API已遷移GraphQL,淘寶也在生成環境有所實踐。

參考文件:


《IVWEB 技術週刊》 震撼上線了,關注公眾號:IVWEB社群,每週定時推送優質文章。

相關文章