【译】更优秀的GraphQL中文文档-服务器端

2,723 阅读8分钟
  • 文档翻译时间: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.cn/post/684490…