本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
GraphQL 类型
GraphQL 支持很多不同的类型。正如您在上一节中看到的一样,类型定义数据的形状或行为。它们是 GraphQL 架构的基本构建块。
类型可以分为输入和输出。输入是允许作为特殊对象类型(Query、Mutation 等)的参数传入的类型,而输出类型严格用于存储和返回数据。下面列出了类型及其分类的列表:
                
                
                
                
                
                
            - 
                  对象:对象包含描述实体的字段。例如,一个对象可能类似于 book,其中包含描述其特性的字段,如authorName、publishingYear等。它们严格来说是输出类型。
 
- 
                  标量:这些是基元类型,如整数、字符串等。它们通常分配给字段。以 authorName字段为例,可以为其分配String标量以存储名称,例如“John Smith”。标量可以是输入类型和输出类型。
 
- 
                  输入:输入允许您传递一组字段以作为参数。它们的结构与对象非常相似,但可以将其作为特殊对象的参数传递。输入允许您在其范围内定义标量、枚举和其他输入。输入只能是输入类型。 
- 
                  特殊对象:特殊对象执行状态更改操作,并完成服务的大部分繁重工作。共有三种特殊对象类型:查询、变更和订阅。查询通常获取数据;变更处理数据;订阅在客户端和服务器之间打开并保持双向连接以进行持续通信。鉴于其功能,特殊对象既不是输入,也不是输出。 
- 
                  枚举:枚举是预定义的合法值列表。如果调用枚举,则枚举值只能是其范围内定义的值。例如,如果您使用一个名为 trafficLights的枚举以描述交通信号列表,它可能具有redLight和greenLight等值,但不能具有purpleLight值。真正的交通信号灯只有那么多信号,因此,您可以使用枚举定义它们,并在引用trafficLight时强制使它们成为唯一的合法值。枚举可以是输入类型和输出类型。
 
- 
                  联合/接口:联合允许您根据客户端请求的数据在请求中返回一个或多个内容。例如,如果您使用具有 title字段的Book类型以及具有name字段的Author类型,您可以在这两种类型之间创建联合。如果您的客户端希望在数据库中查询“Julius Caesar”,则联合可能会从Booktitle返回 Julius Caesar(威廉·莎士比亚的戏剧),并从Authorname返回 Julius Caesar(Commentarii de Bello Gallico 的作者)。联合只能是输出类型。
 接口是对象必须实施的字段集。这有点类似于 Java 等编程语言中的接口,您必须实施接口中定义的字段。例如,假设您创建了一个名为 Book的接口,其中包含一个title字段。假设您后来创建了一个名为Novel的类型,该类型实施Book。Novel必须包含一个title字段。不过,Novel可能还包含接口中不包含的其他字段,例如pageCount或ISBN。接口只能是输出类型。
 
 以下几节介绍了每种类型在 GraphQL 中的工作方式。
               对象
               GraphQL 对象是您在生产代码中看到的主要类型。在 GraphQL 中,您可以将对象视为不同字段的分组(类似于其他语言中的变量),每个字段由可以保存值的类型(通常是标量或另一个对象)定义。对象表示可以从服务实施中检索/处理的数据单元。
               对象类型是使用 Type 关键字声明的。让我们稍微修改一下架构示例:
               type Person {
  id: ID!
  name: String
  age: Int
  occupation: Occupation
}
type Occupation {
  title: String
}
               此处的对象类型是 Person 和 Occupation。每个对象具有自己的字段和自己的类型。GraphQL 的一项功能是,能够将字段设置为其他类型。您可以看到 Person 中的 occupation 字段包含 Occupation 对象类型。我们可以建立这种关联,因为 GraphQL 仅描述数据,而不描述服务实施。
             
               标量
               标量本质上是保存值的基元类型。在中 AWS AppSync,有两种类型的标量:默认的 GraphQL 标量和标量 AWS AppSync 。标量通常用于存储对象类型中的字段值。默认 GraphQL 类型包括 Int、Float、String、Boolean 和 ID。让我们再次使用上一示例:
               type Person { 
  id: ID!
  name: String
  age: Int
  occupation: Occupation
}
type Occupation {
  title: String
}
               挑出 name 和 title 字段,两者都包含 String 标量。Name 可能返回类似于 "John Smith" 的字符串值,title 可能返回类似于 "firefighter" 的值。一些 GraphQL 实施还支持使用 Scalar 关键字并实施类型行为的自定义标量。不过, AWS AppSync 目前不支持自定义标量。有关标量的列表,请参阅 AWS AppSync中的标量类型。
             
               
               由于输入和输出类型概念,在传入参数时存在特定的限制。通常需要传入的类型(尤其是对象)受到限制。您可以使用输入类型绕过该规则。输入是包含标量、枚举和其他输入类型的类型。
               输入是使用 input 关键字定义的:
               type Person { 
  id: ID!
  name: String
  age: Int
  occupation: Occupation
}
type Occupation {
  title: String
}
input personInput { 
  id: ID!
  name: String
  age: Int
  occupation: occupationInput
}
input occupationInput {
  title: String
}
               正如您看到的一样,我们可以使用模仿原始类型的单独输入。这些输入通常在您的字段操作中使用,如下所示:
               type Person { 
  id: ID!
  name: String
  age: Int
  occupation: Occupation
}
type Occupation {
  title: String
}
input occupationInput {
  title: String
}
type Mutation {
  addPerson(id: ID!, name: String, age: Int, occupation: occupationInput): Person
}
               请注意,我们仍然可以传递 occupationInput(替代 Occupation)以创建 Person。
               这只是输入的一种场景。它们不一定需要以 1:1 的方式复制对象;在生产代码中,您很可能不会像这样使用该参数。一种利用 GraphQL 架构的很好做法是,仅定义需要作为参数输入的内容。
               此外,可以在多个操作中使用相同的输入,但我们不建议这样做。理想情况下,每个操作应包含自己的唯一输入副本,以防架构的要求发生变化。
             
               特殊对象
               GraphQL 为特殊对象保留一些关键字,这些对象定义架构如何检索/处理数据的一些业务逻辑。最多可以在架构中包含其中的关键字之一。它们充当客户端对 GraphQL 服务运行的所有请求的数据的入口点。
               也可以使用 type 关键字定义特殊对象。尽管它们的使用方式与常规对象类型不同,但它们的实施非常相似。
               
                  - Queries
- 
                        查询与 GET操作非常相似,因为它们执行只读获取以从源中获取数据。在 GraphQL 中,Query定义向您的服务器发出请求的客户端的所有入口点。在您的 GraphQL 实施中始终具有一个Query。
 以下是我们在以前的架构示例中使用的 Query和修改的对象类型:
 type Person { 
  id: ID!
  name: String
  age: Int
  occupation: Occupation
}
type Occupation {
  title: String
}
type Query {                                   
  people: [Person]
}
 我们的 Query包含一个名为people的字段,该字段从数据来源中返回Person实例列表。假设我们需要更改应用程序的行为,现在我们需要返回仅包含Occupation实例的列表以用于某些单独的用途。我们可以直接将其添加到查询中:
 type Query {                                   
  people: [Person]
  occupations: [Occupation]
}
 在 GraphQL 中,我们可以将查询视为单一请求来源。如您所见,这可能比可能使用不同端点来 RESTful 实现相同目标(.../api/1/people和.../api/1/occupations)的实现要简单得多。
 假设我们具有该查询的解析器实施,我们现在可以执行实际的查询。虽然 Query类型存在,但我们必须明确调用该类型,才能在应用程序的代码中运行。可以使用query关键字完成该操作:
 query getItems {
   people {
      name
   }
   occupations {
      title
   }
}
 正如您看到的一样,该查询命名为 getItems并返回people(Person对象列表)和occupations(Occupation对象列表)。在people中,我们仅返回每个Person的name字段,同时返回每个Occupation的title字段。响应可能如下所示:
 {
  "data": {
    "people": [
      {
        "name": "John Smith"
      },
      {
        "name": "Andrew Miller"
      },
      .
      .
      .
    ],
    "occupations": [
      {
        "title": "Firefighter"
      },
      {
        "title": "Bookkeeper"
      },
      .
      .
      .
    ]
  }
}
 该示例响应说明了数据如何遵循查询的形状。检索的每个条目在该字段的范围内列出。people和occupations是作为单独的列表返回的。虽然这很有用,但修改查询以返回人员姓名和职业的列表可能会更方便:
 query getItems {
   people {
      name   
      occupation {
        title
      }
}
 这是合法的修改,因为我们的 Person类型包含Occupation类型的occupation字段。在people范围内列出时,我们将返回每个Person的name及其按title关联的Occupation。响应可能如下所示:
 }
  "data": {
    "people": [
      {
        "name": "John Smith",
        "occupation": {
          "title": "Firefighter"
        }
      },
      {
        "name": "Andrew Miller",
        "occupation": {
          "title": "Bookkeeper"
        }
      },
      .
      .
      .
    ]
  }
}
 
- Mutations
- 
                        变更类似于 PUT或POST等状态更改操作。它们执行写入操作以修改源中的数据,然后获取响应。它们定义数据修改请求的入口点。与查询不同,根据项目的需求,变更可能会包含在架构中,也可能不会包含在架构中。以下是架构示例中的变更:
 type Mutation {
  addPerson(id: ID!, name: String, age: Int): Person
}
 addPerson字段表示将Person添加到数据来源的一个入口点。addPerson是字段名称;id、name和age是参数;Person是返回类型。回顾一下Person类型:
 type Person { 
  id: ID!
  name: String
  age: Int
  occupation: Occupation
}
 我们添加了 occupation字段。不过,我们不能直接将该字段设置为Occupation,因为不能将对象作为参数传入;它们严格来说是输出类型。我们应该将具有相同字段的输入作为参数传递:
 input occupationInput {
  title: String
}
  在创建新的 Person实例时,我们也可以轻松更新addPerson以将其包含为参数:
 type Mutation {
  addPerson(id: ID!, name: String, age: Int, occupation: occupationInput): Person
}
 以下是更新的架构: type Person { 
  id: ID!
  name: String
  age: Int
  occupation: Occupation
}
type Occupation {
  title: String
}
input occupationInput {
  title: String
}
type Mutation {
  addPerson(id: ID!, name: String, age: Int, occupation: occupationInput): Person
}
 请注意,occupation从occupationInput传入title字段,以完成创建Person而不是原始Occupation对象。假设我们具有addPerson的解析器实施,我们现在可以执行实际的变更。虽然Mutation类型存在,但我们必须明确调用该类型,才能在应用程序的代码中运行。可以使用mutation关键字完成该操作:
 mutation createPerson {
  addPerson(id: ID!, name: String, age: Int, occupation: occupationInput) {
    name
    age
    occupation {
      title
    }
  }
}
 该变更命名为 createPerson,addPerson是操作。要创建新的Person,我们可以输入id、name、age和occupation的参数。在addPerson的范围内,我们还可以看到其他字段,如name、age等。这是您的响应;这些是addPerson操作完成后返回的字段。以下是示例的最后一部分:
 mutation createPerson {
  addPerson(id: "1", name: "Steve Powers", age: "50", occupation: "Miner") {
    id
    name
    age
    occupation {
      title
    }
  }
}
 在使用该变更时,结果可能如下所示: {
  "data": {
    "addPerson": {
      "id": "1",
      "name": "Steve Powers",
      "age": "50",
      "occupation": {
        "title": "Miner"
      }
    }
  }
}
 正如您看到的一样,响应使用与变更中定义的相同格式返回我们请求的值。最好返回所有修改的值,以减少混乱以及将来需要进行更多查询。变更允许您在其范围内包含多个操作。它们按照变更中列出的顺序依次运行。例如,如果我们创建另一个名为 addOccupation的操作以将职务添加到数据来源中,我们可以在变更中的addPerson之后调用该操作。先处理addPerson,然后处理addOccupation。
 
- Subscriptions
- 
                        订WebSockets阅用于在服务器与其客户端之间建立持久的双向连接。通常,客户端订阅或侦听服务器。每次服务器进行服务器端更改或执行事件时,订阅的客户端都会收到更新。如果订阅了多个客户端,并且需要向它们通知服务器或其他客户端中发生的更改,这种类型的协议是非常有用的。例如,可以使用订阅更新社交媒体源。可能具有两个用户(用户 A 和用户 B),他们订阅了自动通知更新,以在每次收到直接消息时收到通知。客户端 A 上的用户 A 可能向客户端 B 上的用户 B 发送直接消息。用户 A 的客户端将发送直接消息,而服务器处理该消息。然后,服务器向用户 B 的账户发送直接消息,同时向客户端 B 发送自动通知。 以下是我们可以添加到架构示例的 Subscription示例:
 type Subscription {                                   
  personAdded: Person
}
 每次将新的 Person添加到数据来源时,personAdded字段都会向订阅的客户端发送消息。假设我们具有personAdded的解析器实施,我们现在可以使用订阅。虽然Subscription类型存在,但我们必须明确调用该类型,才能在应用程序的代码中运行。可以使用subscription关键字完成该操作:
 subscription personAddedOperation {
  personAdded {
    id
    name
  }
}
 订阅命名为 personAddedOperation,操作为personAdded。personAdded将返回新Person实例的id和name字段。看一下变更示例,我们使用该操作添加了一个Person:
 addPerson(id: "1", name: "Steve Powers", age: "50", occupation: "Miner")
 如果我们的客户端订阅了新添加的 Person的更新,它们可能会在addPerson运行后看到这一点:
 {
  "data": {
    "personAdded": {
      "id": "1",
      "name": "Steve Powers"
    }
  }
}
 以下是订阅提供的内容摘要: 订阅是双向通道,允许客户端和服务器接收快速但稳定的更新。他们通常使用该 WebSocket 协议,该协议可创建标准化和安全的连接。 订阅非常灵活,因为它们减少了连接建立开销。在订阅后,客户端可以在较长时间内持续运行该订阅。它们通常允许开发人员定制订阅生命周期并配置请求哪些信息,从而高效地使用计算资源。 一般来说,订阅允许客户端一次进行多个订阅。实际上 AWS AppSync,订阅仅用于接收来自该 AWS AppSync 服务的实时更新。它们不能用于执行查询或变更。 订阅的主要替代方案是轮询,后者按设置的间隔发送查询以请求数据。该过程通常比订阅效率低,并且给客户端和后端带来很大压力。 
我们的架构示例中没有提到的一件事是,还必须在 schema 根中定义您的特殊对象类型。因此,当你在中导出架构时 AWS AppSync,它可能看起来像这样:
               
                  - schema.graphql
- 
                        schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}
.
.
.
type Query {                                   
  # code goes here
}
type Mutation {                                   
  # code goes here
}
type Subscription {                                   
  # code goes here
}
 
枚举
               枚举是特殊标量,它限制类型或字段可能具有的合法参数。这意味着每次在架构中定义枚举时,其关联类型或字段仅限于枚举中的值。枚举被序列化为字符串标量。请注意,不同的编程语言可能以不同的方式处理 GraphQL 枚举。例如, JavaScript 没有原生枚举支持,因此枚举值可以改为映射到 int 值。
               枚举是使用 enum 关键字定义的。示例如下:
               enum trafficSignals {
  solidRed
  solidYellow
  solidGreen
  greenArrowLeft
  ...
}
               在调用 trafficLights 枚举时,参数只能是 solidRed、solidYellow、solidGreen 等。通常使用枚举描述具有不同但有限数量的选项的内容。
             
               联合/接口
               请参阅 GraphQL 中的接口和联合。