

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# GraphQL 类型
<a name="graphql-types"></a>

GraphQL 支持很多不同的类型。正如您在上一节中看到的一样，类型定义数据的形状或行为。它们是 GraphQL 架构的基本构建块。

类型可以分为输入和输出。输入是允许作为特殊对象类型（`Query`、`Mutation` 等）的参数传入的类型，而输出类型严格用于存储和返回数据。下面列出了类型及其分类的列表：
+ **对象**：对象包含描述实体的字段。例如，一个对象可能类似于 `book`，其中包含描述其特性的字段，如 `authorName`、`publishingYear` 等。它们严格来说是输出类型。
+ **标量**：这些是基元类型，如整数、字符串等。它们通常分配给字段。以 `authorName` 字段为例，可以为其分配 `String` 标量以存储名称，例如“John Smith”。标量可以是输入类型和输出类型。
+ **输入**：输入允许您传递一组字段以作为参数。它们的结构与对象非常相似，但可以将其作为特殊对象的参数传递。输入允许您在其范围内定义标量、枚举和其他输入。输入只能是输入类型。
+ **特殊对象**：特殊对象执行状态更改操作，并完成服务的大部分繁重工作。共有三种特殊对象类型：查询、变更和订阅。查询通常获取数据；变更处理数据；订阅在客户端和服务器之间打开并保持双向连接以进行持续通信。鉴于其功能，特殊对象既不是输入，也不是输出。
+ **枚举**：枚举是预定义的合法值列表。如果调用枚举，则枚举值只能是其范围内定义的值。例如，如果您使用一个名为 `trafficLights` 的枚举以描述交通信号列表，它可能具有 `redLight` 和 `greenLight` 等值，但不能具有 `purpleLight` 值。真正的交通信号灯只有那么多信号，因此，您可以使用枚举定义它们，并在引用 `trafficLight` 时强制使它们成为唯一的合法值。枚举可以是输入类型和输出类型。
+ **联合/接口**：联合允许您根据客户端请求的数据在请求中返回一个或多个内容。例如，如果您使用具有 `title` 字段的 `Book` 类型以及具有 `name` 字段的 `Author` 类型，您可以在这两种类型之间创建联合。如果您的客户端希望在数据库中查询“Julius Caesar”，则联合可能会从 `Book` `title` 返回 *Julius Caesar*（威廉·莎士比亚的戏剧），并从 `Author` `name` 返回 *Julius Caesar*（*Commentarii de Bello Gallico* 的作者）。联合只能是输出类型。

  接口是对象必须实施的字段集。这有点类似于 Java 等编程语言中的接口，您必须实施接口中定义的字段。例如，假设您创建了一个名为 `Book` 的接口，其中包含一个 `title` 字段。假设您后来创建了一个名为 `Novel` 的类型，该类型实施 `Book`。`Novel` 必须包含一个 `title` 字段。不过，`Novel` 可能还包含接口中不包含的其他字段，例如 `pageCount` 或 `ISBN`。接口只能是输出类型。

以下几节介绍了每种类型在 GraphQL 中的工作方式。

## 对象
<a name="object-components"></a>

GraphQL 对象是您在生产代码中看到的主要类型。在 GraphQL 中，您可以将对象视为不同字段的分组（类似于其他语言中的变量），每个字段由可以保存值的类型（通常是标量或另一个对象）定义。对象表示可以 retrieved/manipulated 来自您的服务实现的数据单元。

对象类型是使用 `Type` 关键字声明的。让我们稍微修改一下架构示例：

```
type Person {
  id: ID!
  name: String
  age: Int
  occupation: Occupation
}

type Occupation {
  title: String
}
```

此处的对象类型是 `Person` 和 `Occupation`。每个对象具有自己的字段和自己的类型。GraphQL 的一项功能是，能够将字段设置为其他类型。您可以看到 `Person` 中的 `occupation` 字段包含 `Occupation` 对象类型。我们可以建立这种关联，因为 GraphQL 仅描述数据，而不描述服务实施。

## 标量
<a name="scalar-components"></a>

标量本质上是保存值的基元类型。在中 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中的标量类型](https://docs.aws.amazon.com//appsync/latest/devguide/scalars.html)。

## 输入
<a name="input-components"></a>

由于输入和输出类型概念，在传入参数时存在特定的限制。通常需要传入的类型（尤其是对象）受到限制。您可以使用输入类型绕过该规则。输入是包含标量、枚举和其他输入类型的类型。

输入是使用 `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 架构的很好做法是，仅定义需要作为参数输入的内容。

此外，可以在多个操作中使用相同的输入，但我们不建议这样做。理想情况下，每个操作应包含自己的唯一输入副本，以防架构的要求发生变化。

## 特殊对象
<a name="special-object-components"></a>

GraphQL 为特殊对象保留了一些关键字，这些对象定义了架构如何处理数据的一些业务逻辑。 retrieve/manipulate 最多可以在架构中包含其中的关键字之一。它们充当客户端对 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](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications)阅用于在服务器与其客户端之间建立持久的双向连接。通常，客户端订阅或侦听服务器。每次服务器进行服务器端更改或执行事件时，订阅的客户端都会收到更新。如果订阅了多个客户端，并且需要向它们通知服务器或其他客户端中发生的更改，这种类型的协议是非常有用的。例如，可以使用订阅更新社交媒体源。可能具有两个用户（用户 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
}
```

------

## 枚举
<a name="enum-components"></a>

枚举是特殊标量，它限制类型或字段可能具有的合法参数。这意味着每次在架构中定义枚举时，其关联类型或字段仅限于枚举中的值。枚举被序列化为字符串标量。请注意，不同的编程语言可能以不同的方式处理 GraphQL 枚举。例如， JavaScript 没有原生枚举支持，因此枚举值可以改为映射到 int 值。

枚举是使用 `enum` 关键字定义的。示例如下：

```
enum trafficSignals {
  solidRed
  solidYellow
  solidGreen
  greenArrowLeft
  ...
}
```

在调用 `trafficLights` 枚举时，参数只能是 `solidRed`、`solidYellow`、`solidGreen` 等。通常使用枚举描述具有不同但有限数量的选项的内容。

## 联合/接口
<a name="union-interface-components"></a>

请参阅 GraphQL 中的[接口和联合](https://docs.aws.amazon.com/appsync/latest/devguide/interfaces-and-unions.html)。