GraphQL LogoGraphQL

架構和類型

在這個頁面,您將瞭解所有需要知道的 GraphQL 類型系統,以及它如何描述可以查詢的資料。由於 GraphQL 可以與任何後端架構或程式語言一起使用,因此我們將避開特定於實作的細節,只討論概念。

類型系統#

如果您之前看過 GraphQL 查詢,您會知道 GraphQL 查詢語言基本上是關於選擇物件上的欄位。因此,例如,在以下查詢中

  1. 我們從一個特殊的「根」物件開始
  2. 我們選擇該物件上的 hero 欄位
  3. 對於 hero 傳回的物件,我們選擇 nameappearsIn 欄位

由於 GraphQL 查詢的形狀與結果非常匹配,因此您可以在不知道太多伺服器的情況下預測查詢將傳回什麼。但是,對於我們可以要求的資料有一個確切的描述很有用 - 我們可以選擇哪些欄位?它們可能會傳回什麼類型的物件?這些子物件上有什麼欄位可用?這就是架構發揮作用的地方。

每個 GraphQL 服務定義一組類型,完整描述您可以在該服務上查詢的可能資料集。然後,當查詢進入時,會根據該架構驗證並執行查詢。

類型語言#

GraphQL 服務可以用任何語言撰寫。由於我們無法依賴特定程式語言語法(例如 JavaScript)來討論 GraphQL 架構,因此我們將定義自己的簡單語言。我們將使用「GraphQL 架構語言」- 它類似於查詢語言,並允許我們以與語言無關的方式討論 GraphQL 架構。

物件類型和欄位#

GraphQL 架構最基本的組成部分是物件類型,它僅表示您可以從服務中擷取的一種類型物件,以及它有哪些欄位。在 GraphQL 架構語言中,我們可能會這樣表示

type Character {
name: String!
appearsIn: [Episode!]!
}

該語言相當易讀,但讓我們來探討一下,以便我們能有共同的詞彙

  • CharacterGraphQL 物件類型,表示它是一種具有某些欄位的類型。架構中的大多數類型將是物件類型。
  • nameappearsInCharacter 類型上的欄位。這表示 nameappearsIn 是在操作 Character 類型的 GraphQL 查詢的任何部分中唯一可能出現的欄位。
  • String 是內建純量類型之一 - 這些類型會解析為單一純量物件,且在查詢中無法有子選取。我們稍後將進一步探討純量類型。
  • String! 表示該欄位為非空值,表示 GraphQL 服務保證在查詢此欄位時,一定會提供一個值。在類型語言中,我們會用驚嘆號表示這些值。
  • [Episode!]! 表示一個 Episode 物件的陣列。由於它也是非空值,因此在查詢 appearsIn 欄位時,您一定會得到一個陣列(包含零個或多個項目)。而且由於 Episode! 也是非空值,因此您一定會得到一個陣列,其每個項目都是 Episode 物件。

現在您知道 GraphQL 物件類型看起來像什麼,以及如何閱讀 GraphQL 類型語言的基本原理。

引數#

GraphQL 物件類型上的每個欄位可以有零個或多個引數,例如以下的 length 欄位

type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}

所有引數都有名稱。與 JavaScript 和 Python 等語言不同,這些語言的功能會採用一個有序引數清單,GraphQL 中的所有引數都特別按名稱傳遞。在此情況下,length 欄位有一個已定義的引數,unit

引數可以是必需的或選擇性的。當一個引數是選擇性的,我們可以定義一個預設值 - 如果未傳遞 unit 引數,它會預設設定為 METER

查詢和變異類型#

架構中的大多數類型都只是正常的物件類型,但在架構中會有兩種特別的類型

schema {
query: Query
mutation: Mutation
}

每個 GraphQL 服務都有 query 類型,可能會有或沒有 mutation 類型。這些類型與一般物件類型相同,但它們很特別,因為它們定義了每個 GraphQL 查詢的進入點。因此,如果您看到類似這樣的查詢

這表示 GraphQL 服務需要具有包含 herodroid 欄位的 Query 類型

type Query {
hero(episode: Episode): Character
droid(id: ID!): Droid
}

變異以類似方式運作 - 您在 Mutation 類型上定義欄位,這些欄位可用作您可以在查詢中呼叫的根變異欄位。

重要的是要記住,除了作為架構的「進入點」的特殊狀態外,QueryMutation 類型與任何其他 GraphQL 物件類型相同,而且它們的欄位運作方式完全相同。

純量類型#

GraphQL 物件類型具有名稱和欄位,但這些欄位在某個時間點必須解析為具體資料。這就是純量類型發揮作用的地方:它們表示查詢的葉節點。

在下列查詢中,nameappearsIn 欄位將解析為純量類型

我們知道這一點,因為這些欄位沒有任何子欄位 - 它們是查詢的葉節點。

GraphQL 內建一組預設純量類型

  • Int:有符號 32 位元整數。
  • Float:有符號雙精度浮點值。
  • String:UTF-8 字元序列。
  • Booleantruefalse
  • ID:ID 純量類型表示唯一識別碼,通常用於重新擷取物件或作為快取的鍵。ID 類型的序列化方式與字串相同;但是,將其定義為 ID 表示它不應供人類閱讀。

在大部分 GraphQL 服務實作中,也有辦法指定自訂純量型別。例如,我們可以定義一個 Date 型別

scalar Date

然後,由我們的實作定義該型別應如何序列化、反序列化和驗證。例如,你可以指定 Date 型別應始終序列化為整數時間戳記,而你的用戶端應知道任何日期欄位都預期採用該格式。

列舉型別#

列舉型別(也稱為 列舉)是一種特殊純量,限制為特定允許值集合。這可讓你

  1. 驗證此型別的任何引數是否為允許值之一
  2. 透過型別系統傳達欄位將永遠是有限值集合之一

以下是 GraphQL 架構語言中列舉定義的範例

enum Episode {
NEWHOPE
EMPIRE
JEDI
}

這表示無論我們在架構中何處使用 Episode 型別,我們都預期它正好是 NEWHOPEEMPIREJEDI 之一。

請注意,各種語言中的 GraphQL 服務實作將有其特定語言處理列舉的方式。在支援列舉為一級公民的語言中,實作可能會利用該功能;在像 JavaScript 這樣不支援列舉的語言中,這些值可能會內部對應到一組整數。然而,這些詳細資料不會洩漏給用戶端,而用戶端可以完全根據列舉值的字串名稱進行操作。

清單和非 Null#

物件型別、純量和列舉是你可以在 GraphQL 中定義的唯一型別種類。不過,當你在架構的其他部分或在查詢變數宣告中使用型別時,你可以套用影響這些值驗證的其他 型別修改器。我們來看一個範例

type Character {
name: String!
appearsIn: [Episode]!
}

在此,我們使用 String 型別,並透過在型別名稱後加上驚嘆號 ! 將其標示為 非 Null。這表示我們的伺服器始終預期為此欄位傳回非 Null 值,而如果最後取得 Null 值,實際上會觸發 GraphQL 執行錯誤,讓用戶端知道發生錯誤。

非 Null 類型修改器也可以在定義欄位引數時使用,如果在 GraphQL 字串或變數中傳遞 null 值作為該引數,GraphQL 伺服器將會回傳驗證錯誤。

清單以類似的方式運作:我們可以使用類型修改器將類型標記為 清單,這表示此欄位將回傳該類型的陣列。在架構語言中,這表示為使用方括號 [] 包住類型。它對引數也有相同作用,驗證步驟會預期該值為陣列。

非 Null 和清單修改器可以結合使用。例如,您可以擁有非 Null 字串的清單

myField: [String!]

這表示清單本身可以為 null,但它不能有任何 null 成員。例如,在 JSON 中

myField: null // valid
myField: [] // valid
myField: ["a", "b"] // valid
myField: ["a", null, "b"] // error

現在,假設我們定義了非 Null 字串清單

myField: [String]!

這表示清單本身不能為 null,但它可以包含 null 值

myField: null // error
myField: [] // valid
myField: ["a", "b"] // valid
myField: ["a", null, "b"] // valid

您可以根據需要任意巢狀任何數量的非 Null 和清單修改器。

介面#

與許多類型系統一樣,GraphQL 支援介面。介面是一種抽象類型,包含一組特定的欄位,類型必須包含這些欄位才能實作介面。

例如,您可以有一個介面 角色,代表星際大戰三部曲中的任何角色

interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}

這表示任何實作 角色 的類型都需要有這些確切的欄位,以及這些引數和回傳類型。

例如,以下是可能實作 角色 的一些類型

type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}

您可以看到這兩種類型都包含 角色 介面中的所有欄位,但也帶入了額外的欄位,總信用星艦主要功能,這些欄位特定於該特定類型的角色。

當您想要傳回一個物件或一組物件,但這些物件可能是多種不同類型時,介面會很有用。

例如,請注意以下查詢會產生錯誤

hero 欄位傳回類型 Character,這表示它可能是 HumanDroid,具體取決於 episode 參數。在上述查詢中,您只能要求存在於 Character 介面上的欄位,其中不包含 primaryFunction

若要要求特定物件類型上的欄位,您需要使用內嵌片段

在查詢指南的內嵌片段區段中進一步了解這一點。

聯合類型#

聯合類型與介面有相似之處;但是,它們缺乏在組成類型之間定義任何共用欄位的能力。

union SearchResult = Human | Droid | Starship

無論我們在架構中傳回什麼 SearchResult 類型,我們都可能得到 HumanDroidStarship。請注意,聯合類型的成員需要是具體物件類型;您無法從介面或其他聯合類型建立聯合類型。

在這種情況下,如果您查詢傳回 SearchResult 聯合類型的欄位,您需要使用內嵌片段才能查詢任何欄位

__typename 欄位會解析為 String,讓您可以在客戶端區分不同的資料類型。

此外,在這種情況下,由於 HumanDroid 共用一個介面 (Character),您可以查詢它們的共用欄位,而無需在多個類型中重複相同的欄位

{
search(text: "an") {
__typename
... on Character {
name
}
... on Human {
height
}
... on Droid {
primaryFunction
}
... on Starship {
name
length
}
}
}

請注意,name 仍指定在 Starship 上,否則它不會顯示在結果中,因為 Starship 不是 Character

輸入類型#

到目前為止,我們只討論過將標量值(如列舉或字串)傳遞為欄位中的參數。但您也可以輕鬆傳遞複雜物件。這在變異的情況下特別有價值,您可能想要傳遞一個要建立的完整物件。在 GraphQL 架構語言中,輸入類型看起來與一般物件類型完全相同,但關鍵字為 input,而不是 type

input ReviewInput {
stars: Int!
commentary: String
}

以下是您如何在變異中使用輸入物件類型

輸入物件類型上的欄位本身可以參考輸入物件類型,但您無法在架構中混合輸入和輸出類型。輸入物件類型也不能在其欄位上有參數。

繼續閱讀 →驗證