变更 (Mutations)

了解如何通过 GraphQL 服务器修改数据

大多数关于 GraphQL 的讨论都集中在数据获取上,但任何完整的数据平台也需要一种修改服务端数据的方法。

在 REST 中,任何请求都可能在服务器上引起副作用,但按照惯例,建议不要使用 GET 请求来修改数据。GraphQL 也是如此——从技术上讲,任何字段解析器(field resolver)都可以实现为引起数据写入——但 GraphQL 规范指出,“除了顶层变更字段之外的字段解析必须始终是无副作用且幂等的。”因此,对于任何符合规范的 GraphQL Schema,只有变更操作中的顶层字段才允许引起副作用。

在此页面中,你将学习如何使用变更操作通过 GraphQL 写入数据,并以支持客户端用例的方式进行操作。

所有适用于查询的 GraphQL 操作特性也适用于变更,因此在继续之前,请先阅读 查询 页面。

添加新数据

使用 REST API 创建新数据时,你会向特定端点发送 POST 请求,并在请求体中包含要创建的实体信息。GraphQL 则采用了不同的方法。

让我们看一个在 Schema 中定义的变更示例

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}
 
input ReviewInput {
  stars: Int!
  commentary: String
}
 
type Mutation {
  createReview(episode: Episode, review: ReviewInput!): Review
}

与查询类似,变更字段被添加到作为 API 入口点的 根操作类型 之一。在这种情况下,我们在 Mutation 类型上定义了 createReview 字段。

变更字段也可以接受参数,你可能会注意到 review 参数的输入类型被设置为 ReviewInput。这被称为 输入对象类型 (Input Object type),它允许我们传入包含变更所需信息的结构化对象,而不仅仅是单个标量值。

与查询一样,如果变更字段返回一个对象类型,则必须在操作中指定其字段的选择集

操作
变量 (Variables)
响应

虽然 createReview 字段可以在 Schema 中定义为任何有效的输出类型,但惯例是指定一个与变更期间修改的内容相关的输出类型——在这种情况下是 Review 类型。这对于需要在更新后获取对象新状态的客户端非常有用。

回想一下,GraphQL 旨在与你现有的代码和数据配合使用,因此当客户端向 GraphQL 服务器发送此操作时,评价的具体创建过程由你决定。在 createReview 变更期间将新评价写入数据库的假设函数可能如下所示

const Mutation = {
  createReview(_obj, args, context, _info) {
    return context.db
      .createNewReview(args.episode, args.review)
      .then(reviewData => new Review(reviewData));
  },
};

你可以在 执行页面 了解更多关于 GraphQL 如何为字段提供数据的信息。

更新现有数据

类似地,我们使用变更来更新现有数据。为了更改一个人的名字,我们将定义一个新的变更字段,并将该字段的输出类型设置为 Human 类型,以便在服务器成功写入数据后,将更新后的人物信息返回给客户端

type Mutation {
  updateHumanName(id: ID!, name: String!): Human
}

此操作将更新 Luke Skywalker 的名字

操作
变量 (Variables)
响应

专用型变更

前面的示例展示了与 REST 的一个重要区别。要使用 REST API 更新人物的属性,你可能会使用 PATCH 请求将任何更新的数据发送到该资源的通用端点。而使用 GraphQL,你可以定义更具体的变更字段,例如专门为当前任务设计的 updateHumanName,而不是简单地创建一个 updateHuman 变更。

专用型变更字段可以让 Schema 更有表现力,允许字段参数的输入类型为非空类型(通用的 updateHuman 变更可能需要接受许多可空参数来处理不同的更新场景)。在 Schema 中定义这些要求还消除了使用其他运行时逻辑来确定是否提交了适当的值以执行客户端所需写入操作的需要。

GraphQL 还允许我们表达数据之间的关系,而这些关系在基本的 CRUD 式请求中很难进行语义化建模。例如,用户可能希望保存电影的个人评分。虽然评分属于用户且不修改电影本身的任何内容,但我们可以按如下方式将其与 Film 对象进行人体工程学关联

操作
变量 (Variables)
响应

作为一般规则,GraphQL API 的设计应该旨在帮助客户端以对他们有意义的方式获取和修改数据,因此 Schema 中定义的字段应该由这些用例来决定。

删除现有数据

就像我们可以发送 DELETE 请求来通过 REST API 删除资源一样,我们也可以通过在 Mutation 类型上定义另一个字段来使用变更删除某些现有数据

type Mutation {
  deleteStarship(id: ID!): ID!
}

这是一个新变更字段的示例

操作
变量 (Variables)
响应

与创建和更新数据的变更一样,GraphQL 规范并未指出删除数据的成功变更操作应该返回什么,但我们必须在 Schema 中为该字段指定某种类型作为输出类型。通常,会使用被删除实体的 ID 或包含该实体数据的有效载荷对象来表示操作成功。

变更中的多个字段

变更可以包含多个字段,就像查询一样。除了名称之外,查询和变更之间还有一个重要的区别

查询字段是并行执行的,而变更字段是串行运行的。

让我们看一个例子:

操作
响应

这些顶层字段的 串行执行 意味着如果我们在一个请求中发送两个 deleteStarship 变更,第一个保证在第二个开始之前完成,确保我们不会与自己产生竞态条件。

请注意,顶层 Mutation 字段的串行执行不同于数据库事务的概念。某些变更字段可能会成功解析,而其他字段则返回错误,当这种情况发生时,GraphQL 无法撤销操作中已成功的部分。因此,在前面的示例中,如果第一个星舰被成功移除,但 secondShip 字段抛出了错误,GraphQL 没有内置的方法来随后撤销 firstShip 字段的执行。

后续步骤

回顾一下我们学到的关于变更的内容

  • 客户端可以使用 GraphQL API 创建、更新和删除数据,具体取决于 Schema 中公开的功能
  • 根据客户端需求,变更可以设计为适应写入操作的细粒度用例
  • Mutation 类型上的顶层字段将串行执行,这与通常并行执行的其他类型的字段不同

现在我们已经了解了如何使用 GraphQL 服务器读取和写入数据,我们准备学习如何使用 订阅 (Subscriptions) 实时获取数据。你可能还想详细了解 GraphQL 查询和变更如何 通过 HTTP 提供服务

下一课

订阅 (Subscriptions)

探索 GraphQL 如何通过订阅支持实时数据,以及如何在大规模环境下有效地使用它们。

前往下一课 教程