学习教程
修改

变更

¥Mutations

Learn how to modify data with a GraphQL server

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

¥Most discussions of GraphQL focus on data fetching, but any complete data platform needs a way to modify server-side data as well.

在 REST 中,任何请求都可能对服务器造成一些副作用,但按照惯例,建议不要使用 GET 请求来修改数据。GraphQL 类似 - 从技术上讲,任何字段解析器都可以实现以导致数据写入 - 但 GraphQL 规范状态 “顶层突变字段以外的字段的解析必须始终无副作用且幂等。” 因此,对于任何符合规范的 GraphQL 模式,只有变异操作中的顶层字段才允许引起副作用。

¥In REST, any request might cause some side-effects on the server, but by convention, it’s suggested that one doesn’t use GET requests to modify data. GraphQL is similar—technically any field resolver could be implemented to cause a data write—but the GraphQL specification states that “the resolution of fields other than top-level mutation fields must always be side effect-free and idempotent.” Thus, for any spec-compliant GraphQL schemas, only the top-level fields in mutation operations are allowed to cause side effects.

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

¥On this page, you’ll learn how to use mutation operations to write data using GraphQL, and do so in a way that supports client use cases.

适用于查询的 GraphQL 操作的所有功能也适用于突变,因此在继续之前请先查看 查询 页面。

¥All of the features of GraphQL operations that apply to queries also apply to mutations, so review the Queries page first before proceeding.

添加新数据

¥Add new data

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

¥When creating new data with a REST API, you would send a POST request to a specific endpoint and include information about the entities to be created in the body of the request. GraphQL takes a different approach.

让我们看一个在我们的架构中定义的示例突变:

¥Let’s look at an example mutation that’s defined in our schema:

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

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

¥Like queries, mutation fields are added to one of the root operation types that provide an entry point to the API. In this case, we define the createReview field on the Mutation type.

突变字段也可以接受参数,你可能会注意到 review 参数的输入类型设置为 ReviewInput。这称为 输入对象类型,它允许我们传入一个结构化对象,其中包含突变可以使用的信息,而不仅仅是单个标量值。

¥Mutation fields can also accept arguments and you might notice that the review argument has an input type set to ReviewInput. This is known as Input Object type, which allows us to pass in a structured object containing information the mutation can use instead of individual scalar values only.

同样,就像查询一样,如果突变字段返回 Object 类型,则你可以在操作中指定其字段的选择集:

¥Also like just like queries, if the mutation field returns an Object type, then you specify a selection set of its fields in the operation:

Operation
Variables
Response

虽然 createReview 字段可以用模式中的任何有效输出类型定义,但通常指定与突变期间修改的任何内容相关的输出类型 - 在本例中为 Review 类型。这对于需要在更新后获取对象新状态的客户端非常有用。

¥While the createReview field could be defined with any valid output type in the schema, it’s conventional to specify an output type that relates to whatever is modified during the mutation—in this case, the Review type. This can be useful for clients that need to fetch the new state of an object after an update.

回想一下,GraphQL 旨在与你现有的代码和数据一起使用,因此当客户端将此操作发送到 GraphQL 服务器时,评论的实际创建取决于你。假设在 createReview 突变期间将新评论写入数据库的函数可能如下所示:

¥Recall that GraphQL is meant to work with your existing code and data, so the actual creation of the review is up to you when clients send this operation to the GraphQL server. A hypothetical function that writes the new review to a database during a createReview mutation might look like this:

Mutation: {
  createReview(obj, args, context, info) {
    return context.db
      .createNewReview(args.episode, args.review)
      .then((reviewData) => new Review(reviewData))
  }
}

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

¥You can learn more about how GraphQL provides data for fields on the Execution page.

更新现有数据

¥Update existing data

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

¥Similarly, we use mutations to update existing data. To change a human’s name, we’ll define a new mutation field and set that field’s output type to the Human type so we can return the updated human’s information to client after the server successfully writes the data:

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

此操作将更新 Luke Skywalker 的名字:

¥This operation will update Luke Skywalker’s name:

Operation
Variables
Response

专门构建的突变

¥Purpose-built mutations

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

¥The previous example demonstrates an important distinction from REST. To update a human’s properties using a REST API, you would likely send any updated data to a generalized endpoint for that resource using a PATCH request. With GraphQL, instead of simply creating an updateHuman mutation, you can define more specific mutation fields such as updateHumanNamethat are designed for the task at hand.

专门构建的突变字段可以通过允许字段参数的输入类型为非空类型来帮助使模式更具表现力(通用 updateHuman 突变可能需要接受许多可空参数来处理不同的更新场景)。在模式中定义此要求还消除了对其他运行时逻辑的需要,以确定已提交适当的值来执行客户端所需的写入操作。

¥Purpose-built mutation fields can help make a schema more expressive by allowing the input types for field arguments to be Non-Null types (a generic updateHuman mutation would likely need to accept many nullable arguments to handle different update scenarios). Defining this requirement in the schema also eliminates the need for other runtime logic to determine that the appropriate values were submitted to perform the client’s desired write operation.

GraphQL 还允许我们表达数据之间的关系,而使用基本的 CRUD 样式请求在语义上建模会更加困难。例如,用户可能希望保存电影的个人评分。虽然评级属于用户并且不会修改与电影本身相关的任何内容,但我们可以符合人机工程学地将其与 Film 对象关联起来,如下所示:

¥GraphQL also allows us to express relationships between data that would be more difficult to model semantically with a basic CRUD-style request. For example, a user may wish to save a personal rating for a film. While the rating belongs to the user and doesn’t modify anything related to a film itself, we can ergonomically associate it with a Film object as follows:

Operation
Variables
Response

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

¥As a general rule, a GraphQL API should be designed to help clients get and modify data in a way that makes sense for them, so the fields defined in a schema should be informed by those use cases.

删除现有数据

¥Remove existing data

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

¥Just as we can send a DELETE request to delete a resource with a REST API, we can use mutations to delete some existing data as well by defining another field on the Mutation type:

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

以下是新突变字段的示例:

¥Here’s an example of the new mutation field:

Operation
Variables
Response

与创建和更新数据的变异一样,GraphQL 规范并未指出从删除数据的成功变异操作中应返回什么,但我们必须指定某种类型作为模式中字段的输出类型。通常,已删除实体的 ID 或包含有关实体的数据的有效负载对象将用于指示操作已成功。

¥As with mutations that create and update data, the GraphQL specification doesn’t indicate what should be returned from a successful mutation operation that deletes data, but we do have to specify some type as an output type for the field in the schema. Commonly, the deleted entity’s ID or a payload object containing data about the entity will be used to indicate that the operation was successful.

变更的多个字段

¥Multiple fields in mutations

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

¥A mutation can contain multiple fields, just like a query. There’s one important distinction between queries and mutations, other than the name:

虽然查询字段是并行执行的,但突变字段是串行运行的。

¥While query fields are executed in parallel, mutation fields run in series.

让我们看一个例子:

¥Let’s look at an example:

Operation
Response

这些顶层字段中的 串行执行 意味着如果我们在一个请求中发送两个 deleteStarship 突变,则保证第一个突变在第二个突变开始之前完成,从而确保我们不会陷入与自己的竞争状态。

¥Serial execution of these top-level fields means that if we send two deleteStarship mutations in one request, the first is guaranteed to finish before the second begins, ensuring that we don’t end up in a race condition with ourselves.

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

¥Note that serial execution of top-level Mutation fields differs from the notion of a database transaction. Some mutation fields may resolve successfully while others return errors, and there’s no way for GraphQL to revert the successful portions of the operation when this happens. So in the previous example, if the first starship is removed successfully but the secondShip field raises an error, there is no built-in way for GraphQL to revert the execution of the firstShip field afterward.

后续步骤

¥Next steps

回顾我们学到的关于自省的知识:

¥To recap what we’ve learned about mutations:

  • 客户端可以使用 GraphQL API 创建、更新和删除数据,具体取决于架构中公开的功能

    ¥Clients can create, update, and delete data using a GraphQL API, depending on what capabilities are exposed in the schema

  • 根据客户端要求,可以设计突变以适应写入操作的细粒度用例

    ¥Depending on client requirements, mutations can be designed to accommodate granular use cases for write operations

  • Mutation 类型的顶层字段将按顺序执行,而其他类型的字段通常并行执行

    ¥Top-level fields on the Mutation type will execute serially, unlike fields on other types which are often executed in parallel

现在我们知道了如何使用 GraphQL 服务器读取和写入数据,我们已准备好学习如何使用 订阅 实时获取数据。你可能还希望了解有关 GraphQL 查询和突变如何成为 通过 HTTP 提供服务 的更多信息。

¥Now that we know how to use a GraphQL server to read and write data, we’re ready to learn how to fetch data in real time using subscriptions. You may also wish to learn more about how GraphQL queries and mutations can be served over HTTP.