【譯】更優秀的GraphQL中文文件-伺服器端

瑞豐發表於2019-03-21
  • 文件翻譯時間:2019年/3月/21日
  • 譯者:賀瑞豐(深度使用過GraqhQL)
  • 目的:提供更接地氣的中文說明 plus:其他的翻譯太爛啦

!歡迎來信與譯者討論GraphQL相關問題 !

Schemas and Types

本文中,你會學到 GraphQL 型別系統的所有細節並且它是如何去描述什麼樣的資料是可以被查詢的。既然GraphQL可以再任何後端框架和程式語言中使用,所以我們暫且不談GraphQL的實現細節,只聚焦於核心概念。

Type System

如果你已經見過GraphQL query,那麼你就知道GraphQL 查詢語言基本上是在物件上查詢指定的欄位。例如

  • query
    {
      hero {
        name
        appearsIn
      }
    }
    複製程式碼
  • result
    {
      "data": {
        "hero": {
          "name": "R2-D2",
          "appearsIn": ["NEWHOPE","EMPIRE","JEDI"]
        }
      }
    }
    複製程式碼
  1. 我們從一個特殊的“根”物件開始
  2. hero 地段上做選擇
  3. 對於 hero 返回的物件,我們選擇 nameappearsIn 欄位

因為 GraphQL 的查詢與結果在結構形式上高度匹配,你就可以預測服務端會返回什麼樣的資料而不用關心服務端具體是怎麼實現的。但是對我們需求的資料做精確的描述是很有用的--也決定了什麼樣的欄位我們可以去查詢?哪一類物件會被返回?在子物件中哪些欄位是可用的?這就是schema的作用。

每一個 GraphQL services 都會定義一個 type 的集合,完整的描述了你可以訪問的資料集合。然後,當接受到查詢時,請求基於 Schema 被檢驗、執行.

Type language

GraphQL services 可以被任何語言實現,既然我們不依賴於一種特定的程式語言的語法,例如JavaScript來講解 GraphQL schemas ,我們會來定義我們自己的簡單語言--"GraphQL schema language",它和QL語言類似,讓我們可以和 GraphQL schemas 良好的溝通。

Object types and fields

GraphQL schema 最基礎的元件是 object types,它標識了你可以從後端服務中獲取哪些物件和子欄位。例如:

type Character {
  name: String!
  appearsIn: [Episode!]!
}
複製程式碼

這種語言可讀性高,但是我們仔細過一下細節,來保持基本的術語理解

  • Character 是一個 GraphQL Object Type ,表示這個物件是擁有某些欄位的,你的 schema 中大部分的types 都是 Object Type
  • nameappearsInCharacter型別,這意味著在 GraphQL query 在操作 ** Character type** 的時候只能使用 name 和 appearsIn。
  • String 是內建的 **scalar types **之一,這種型別resolve to a single scalar object,並且沒有次級選擇。我們後面詳細討論
  • String! 說明該欄位必填,也就是說在你發起GraphQL query時,該欄位必須是有值的,在型別語言中,我們用感嘆號來標識。
  • [Episode!]! 代表著 Episode objects的陣列,並且是非空陣列,而且請求 appearsIn 欄位的時候必須傳一個陣列,資料裡面每個資料都必須是 Episode 型別的。

現在你知道了 GraphQL object type 是什麼樣子的,並且如何去閱讀這門語言。

Arguments

每一個 GraphQL object type 都可以有引數,例如:

type Starship {
  id: ID!
  name: String!
  length(unit: LengthUnit = METER): Float
}
複製程式碼

每個引數都有名字。不像 JavaScript 和 Python 函式接收一個有序的引數集合,GrapphQL中的引數傳遞時都指定了名稱,在上例中,length 欄位有一個定義好的引數 unit.

引數可以是必須的也可以是可選的,當引數是可選時,我們提前定義一個預設值--如果 unit 引數沒有傳值,它會使用預設值 METER。

The Query and Mutation types

在你的 schema 中有兩種型別是非常特殊的

schema {
  query: Query
  mutation: Mutation
}
複製程式碼

每一個 GraphQL service 都有一個 query type,mutation type不一定都有,他們特殊在-定義了每一個 GraphQl query 的入口 當你看到如下的query的時候

query {
  hero {
    name
  }
  droid(id: "2000") {
    name
  }
}
複製程式碼

意味著 你的GraphQL service必須有一個 Query type 包含了 hero and droid 欄位

type Query {
  hero(episode: Episode): Character
  droid(id: ID!): Droid
}
複製程式碼

Mutations也是類似的方式,你在 Mutation type 上定義欄位,這些欄位就是你執行mutation時的入口。

要記住:Query、Mutation types除了可以定義入口之外,和普通的 object type 沒有什麼區別。

Scalar types

一個 GraphQL object type 具有名字和欄位,但是在某些時候,這些欄位必須解析成某些具體資料。 例如 appearsIn 欄位被返回 [ "NEWHOPE","EMPIRE","JEDI"]陣列。

GraqhQL內建了一個 scalar type 的集合

  • Int :32位有符號整數
  • Float : 雙精度有符號浮點數
  • String : UTF-8 字串
  • Boolean: true of false
  • ID: 代表一個特殊的標識,經常用於獲取某個特定的物件,或者作為快取中的 key 標識。ID 型別和 String 使用一樣的方式來 serialize;但是當我們定義ID時並不要求其可讀性高

在大多數 GraphQL service 實現中,經常會有一個特殊的自定義的 scalar type.例如 我們可以定義一個 Date type:

scalar Date
複製程式碼

然後我們就個可以自己來定義如何去serialize,deserialized 和 validate 這種型別了。例如,你可以指定 Date type被 serialize成一個 整數時間戳。

Enumeration types

也被叫做Enums,這種型別是一種特殊的 scalar types,只能在特定的值的集合中選擇。這樣的作用是

  • 驗證這種型別的引數只能是特定的某幾個值
  • 整個型別系統中,該欄位始終只有有限的幾個值可選

下面是 一個列舉定義在 GraphQL schema language 是什麼樣的?

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}
複製程式碼

這以為這無論你在哪裡使用 type Episode,我們認為它只有上述的那幾個值。

注意:各種語言實現的 GraphQL service 都有自己處理列舉型別的方式。在那種將enums視作一等公民的語言中,這種實現可以利用上述特性。但是在像JavaScript這種對 enum 沒有支援多點語言中,其值可能被對映為一系列的整數集合。然後客戶端是不會知道這些細節的,客戶端完全依照enum值的字串名稱來操作(譯者注:這裡我也沒太懂,等用了nodejs實現一遍,再回來補充)

Lists and Non-Null

這一段屬於廢話又多,知識點又可以從前面推理得到的。就不做翻譯了,其實可以跳過這裡。如果有不放心的話可以參看原文

Interfaces

和其他型別系統一樣,GraphQL也支援 Interfaces. 一個 interface 是一個抽象的 type,它包含了一個確定的欄位集,當你做 type a inmlements b(interface)這樣的操作時,a型別就必須包含b介面中定義的欄位。 我們舉個例子來看一下

提前定義一個介面,等待其他的 type 來 implement,

interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}
複製程式碼

任何implement Character的型別都必須有上述的欄位和引數,如下所示

type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  # 上面是Character介面的
  starships: [Starship]
  totalCredits: Int
}

type Droid implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  # 上面是Character介面的
  primaryFunction: String
}
複製程式碼

除了Character 介面定義的欄位外,Human 和 Droid還可以有自己定義的欄位。

介面在你想返回一個物件或者一系列object type的時候有用。

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
  }
}
// 變數
{
  "ep": "JEDI"
}
複製程式碼

hero欄位提前定義好了返回一個 Character type,而且在後端實現中只有Human或者Droid implement 了 Character type,所以對於Character type內建好了的欄位-比如name,你可以直接獲取,但是其他實現層特定的欄位比如 Droid 上的primaryFunction就必須採用內聯片段來獲取了

Learn more about this in the inline fragments section in the query guide.

譯者注:介面是用來做抽象的 type,所有對介面的實現的 type 都具有公共欄位

Union types

Union type 和介面型別非常相似,但是並沒有指定type之間的公共欄位

union SearchResult = Human | Droid | Starship
複製程式碼

無論什麼時候我們 return SearchResult type,我們可以獲得上述的三個 type.注意union type必須有實際的 object type組成,不能由其他的union type 或者介面組成。

在本例中,如果你查詢的欄位返回的是 SerchResult union type ,也需要用到內聯片段

  • query
    {
      search(text: "an") {
        __typename
        ... on Human {
          name
          height
        }
        ... on Droid {
          name
          primaryFunction
        }
        ... on Starship {
          name
          length
        }
      }
    }
    複製程式碼
  • result
{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo",
        "height": 1.8
      },
      {
        "__typename": "Human",
        "name": "Leia Organa",
        "height": 1.5
      },
      {
        "__typename": "Starship",
        "name": "TIE Advanced x1",
        "length": 9.2
      }
    ]
  }
}
複製程式碼

__typename 欄位返回 String ,作為一個識別符號來與其他的data type做區分。

並且,本例中,既然 HumanDroid 都有一個共同的介面(Character),你可以直接查詢他們的公共欄位而不是重複的在其中查詢。

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

這裡注意 nameStarship中仍然要被指定,否則查詢結果中不會出現 **name **,因為它並不享有同樣的介面。

Input types

目前為止,我們只討論過傳遞 scalar values,比如 enums 或者 strings 作為引數。但是你可以傳遞複雜的物件作為引數。這在 mutation 中非常有用,你嚐嚐會傳遞一個大的物件給伺服器。在 GraphQL schema language 中,input type 和其他的常規的 object types一樣,但是不是用 type 關鍵字了,而是用 input 作為關鍵字。

input ReviewInput {
  stars: Int!
  commentary: String
}
複製程式碼

下面是一個使用示例

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
// 變數
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
// 返回的資料
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}
複製程式碼

input object type 上的欄位也可以指向其他的 input object type (譯者注:物件巢狀來組合成更加複雜的引數結構),但是不能把 input / output type搞混了。input object type在欄位上是不能支援引數傳遞的。

!!!全文完 !!! 都看到這裡啦,翻譯不易,請留下您的?吧!這會成為我持續提供優質文章的動力

客戶端的文件也翻譯好了:juejin.im/post/5c9247…

相關文章