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

Posted 掘金开发者社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了译更优秀的GraphQL中文文档-服务器端相关的知识,希望对你有一定的参考价值。

  • 文档翻译时间:2019年/3月/21日

  • 译者:贺瑞丰(深度使用过 GraqhQL)

  • 目的:提供更接地气的中文说明 plus:其他的翻译太烂啦

!欢迎来信与译者讨论 GraphQL 相关问题 !

Schemas and Types

本文中,你会学到 GraphQL 类型系统的所有细节并且它是如何去描述什么样的数据是可以被查询的。既然 GraphQL 可以再任何后端框架和编程语言中使用,所以我们暂且不谈 GraphQL 的实现细节,只聚焦于核心概念。

Type System

如果你已经见过 GraphQL query,那么你就知道 GraphQL 查询语言基本上是在对象上查询指定的字段。例如

  • query 

 
   
   
 
  1. {

  2. hero {

  3. name

  4. appearsIn

  5. }

  6. }

  • result

 
   
   
 
  1. {

  2. "data": {

  3. "hero": {

  4. "name": "R2-D2",

  5. "appearsIn": ["NEWHOPE","EMPIRE","JEDI"]

  6. }

  7. }

  8. }


  1. 我们从一个特殊的“根”对象开始

  2. 在 hero 地段上做选择

  3. 对于 hero 返回的对象,我们选择 name 和 appearsIn 字段

因为 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,它标识了你可以从后端服务中获取哪些对象和子字段。例如:

 
   
   
 
  1. type Character {

  2. name: String!

  3. appearsIn: [Episode!]!

  4. }

这种语言可读性高,但是我们仔细过一下细节,来保持基本的术语理解

  • Character 是一个 GraphQL Object Type ,表示这个对象是拥有某些字段的,你的 schema 中大部分的 types 都是 Object Type

  • name 和 appearsIn 是 Character类型,这意味着在 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 都可以有参数,例如:

 
   
   
 
  1. type Starship {

  2. id: ID!

  3. name: String!

  4. length(unit: LengthUnit = METER): Float

  5. }

每个参数都有名字。不像 JavaScript 和 Python 函数接收一个有序的参数集合,GrapphQL 中的参数传递时都指定了名称,在上例中,length 字段有一个定义好的参数 unit.

参数可以是必须的也可以是可选的,当参数是可选时,我们提前定义一个默认值--如果 unit 参数没有传值,它会使用默认值 METER。

The Query and Mutation types

在你的 schema 中有两种类型是非常特殊的

 
   
   
 
  1. schema {

  2. query: Query

  3. mutation: Mutation

  4. }

每一个 GraphQL service 都有一个 query type,mutation type不一定都有,他们特殊在-定义了每一个 GraphQl query 的入口 当你看到如下的 query 的时候

 
   
   
 
  1. query {

  2. hero {

  3. name

  4. }

  5. droid(id: "2000") {

  6. name

  7. }

  8. }

意味着 你的 GraphQL service 必须有一个 Query type 包含了 hero and droid 字段

 
   
   
 
  1. type Query {

  2. hero(episode: Episode): Character

  3. droid(id: ID!): Droid

  4. }

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:

 
   
   
 
  1. scalar Date

然后我们就个可以自己来定义如何去 serialize,deserialized 和 validate 这种类型了。例如,你可以指定 Date type 被 serialize 成一个 整数时间戳。

Enumeration types

也被叫做 Enums,这种类型是一种特殊的 scalar types,只能在特定的值的集合中选择。这样的作用是

  • 验证这种类型的参数只能是特定的某几个值

  • 整个类型系统中,该字段始终只有有限的几个值可选

下面是 一个枚举定义在 GraphQL schema language 是什么样的?

 
   
   
 
  1. enum Episode {

  2. NEWHOPE

  3. EMPIRE

  4. JEDI

  5. }

这以为这无论你在哪里使用 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,

 
   
   
 
  1. interface Character {

  2. id: ID!

  3. name: String!

  4. friends: [Character]

  5. appearsIn: [Episode]!

  6. }

任何 implement Character 的类型都必须有上述的字段和参数,如下所示

 
   
   
 
  1. type Human implements Character {

  2. id: ID!

  3. name: String!

  4. friends: [Character]

  5. appearsIn: [Episode]!

  6. # 上面是Character接口的

  7. starships: [Starship]

  8. totalCredits: Int

  9. }


  10. type Droid implements Character {

  11. id: ID!

  12. name: String!

  13. friends: [Character]

  14. appearsIn: [Episode]!

  15. # 上面是Character接口的

  16. primaryFunction: String

  17. }

除了 Character 接口定义的字段外,Human 和 Droid 还可以有自己定义的字段。

接口在你想返回一个对象或者一系列 object type 的时候有用。

 
   
   
 
  1. query HeroForEpisode($ep: Episode!) {

  2. hero(episode: $ep) {

  3. name

  4. ... on Droid {

  5. primaryFunction

  6. }

  7. }

  8. }

  9. // 变量

  10. {

  11. "ep": "JEDI"

  12. }

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 之间的公共字段

 
   
   
 
  1. union SearchResult = Human | Droid | Starship

无论什么时候我们 return SearchResult type,我们可以获得上述的三个 type.注意 union type 必须有实际的 object type 组成,不能由其他的 union type 或者接口组成。

在本例中,如果你查询的字段返回的是 SerchResult union type ,也需要用到内联片段

  • query 

 
   
   
 
  1. {

  2. search(text: "an") {

  3. __typename

  4. ... on Human {

  5. name

  6. height

  7. }

  8. ... on Droid {

  9. name

  10. primaryFunction

  11. }

  12. ... on Starship {

  13. name

  14. length

  15. }

  16. }

  17. }

  • result

 
   
   
 
  1. {

  2. "data": {

  3. "search": [

  4. {

  5. "__typename": "Human",

  6. "name": "Han Solo",

  7. "height": 1.8

  8. },

  9. {

  10. "__typename": "Human",

  11. "name": "Leia Organa",

  12. "height": 1.5

  13. },

  14. {

  15. "__typename": "Starship",

  16. "name": "TIE Advanced x1",

  17. "length": 9.2

  18. }

  19. ]

  20. }

  21. }

__typename 字段返回 String ,作为一个标识符来与其他的 data type 做区分。

并且,本例中,既然 Human 和 Droid 都有一个共同的接口(Character),你可以直接查询他们的公共字段而不是重复的在其中查询。

 
   
   
 
  1. {

  2. search(text: "an") {

  3. __typename

  4. ... on Character {

  5. name

  6. }

  7. ... on Human {

  8. height

  9. }

  10. ... on Droid {

  11. primaryFunction

  12. }

  13. ... on Starship {

  14. name

  15. length

  16. }

  17. }

  18. }

这里注意 name 在 Starship中仍然要被指定,否则查询结果中不会出现 name因为它并不享有同样的接口。

Input types

目前为止,我们只讨论过传递 scalar values,比如 enums 或者 strings 作为参数。但是你可以传递复杂的对象作为参数。这在 mutation 中非常有用,你尝尝会传递一个大的对象给服务器。在 GraphQL schema language 中,input type 和其他的常规的 object types一样,但是不是用 type 关键字了,而是用 input 作为关键字。

 
   
   
 
  1. input ReviewInput {

  2. stars: Int!

  3. commentary: String

  4. }

下面是一个使用示例

 
   
   
 
  1. mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {

  2. createReview(episode: $ep, review: $review) {

  3. stars

  4. commentary

  5. }

  6. }

  7. // 变量

  8. {

  9. "ep": "JEDI",

  10. "review": {

  11. "stars": 5,

  12. "commentary": "This is a great movie!"

  13. }

  14. }

  15. // 返回的数据

  16. {

  17. "data": {

  18. "createReview": {

  19. "stars": 5,

  20. "commentary": "This is a great movie!"

  21. }

  22. }

  23. }

input object type 上的字段也可以指向其他的 input object type (译者注:对象嵌套来组合成更加复杂的参数结构),但是不能把 input / output type搞混了。input object type在字段上是不能支持参数传递的。

!!!全文完 !!! 都看到这里啦,翻译不易,请留下您的

以上是关于译更优秀的GraphQL中文文档-服务器端的主要内容,如果未能解决你的问题,请参考以下文章

Word 文档的优秀代码片段工具或插件?

nestjs 是不是支持 graphql 片段?

[Async-graphql]中文文档

在 Java 的 GraphQL 查询中添加片段

为啥 GraphQL 片段在查询中需要 __typename?

第十期基于 ApolloKoa 搭建 GraphQL 服务端