学习教程
全局对象识别

全局对象识别

¥Global Object Identification

一致的对象访问支持简单的缓存和对象查找

¥Consistent object access enables simple caching and object lookups

为了为 GraphQL 客户端提供优雅地处理缓存和数据重新获取的选项,GraphQL 服务器需要以标准化方式公开对象标识符。

¥To provide options for GraphQL clients to elegantly handle caching and data refetching, GraphQL servers need to expose object identifiers in a standardized way.

为此,客户端需要通过标准机制进行查询,以通过 ID 请求对象。然后,在响应中,结构需要提供提供这些 ID 的标准方法。

¥For this to work, a client will need to query via a standard mechanism to request an object by ID. Then, in the response, the schema will need to provide a standard way of providing these IDs.

由于除了 ID 之外,我们对对象知之甚少,因此我们将这些对象称为 “节点。” 下面是节点查询的示例:

¥Because little is known about the object other than its ID, we call these objects “nodes.” Here is an example query for a node:

{
  node(id: "4") {
    id
    ... on User {
      name
    }
  }
}
  • GraphQL 结构的格式设置为允许通过根查询对象上的 node 字段获取任何对象。这将返回符合 “Node” interface 的对象。

    ¥The GraphQL schema is formatted to allow fetching any object via the node field on the root query object. This returns objects which conform to a “Node” interface.

  • 可以安全地从响应中提取 id 字段,并可以通过缓存和重新获取进行存储以供重复使用。

    ¥The id field can be extracted out of the response safely, and can be stored for re-use via caching and refetching.

  • 客户端可以使用接口片段来提取特定于符合节点接口的类型的附加信息。在本例中为 “用户”。

    ¥Clients can use interface fragments to extract additional information specific to the type which conform to the node interface. In this case a “User”.

节点界面如下所示:

¥The Node interface looks like:

# An object with a Globally Unique ID
interface Node {
  # The ID of the object.
  id: ID!
}

用户通过以下方式遵守:

¥With a User conforming via:

type User implements Node {
  id: ID!
  # Full name
  name: String!
}

规范

¥Specification

下面的所有内容都以更正式的要求描述了围绕对象识别的规范,以确保跨服务器实现的一致性。这些规范基于服务器如何与 Relay API 客户端兼容,但对任何客户端都很有用。

¥Everything below describes with more formal requirements a specification around object identification in order to conform to ensure consistency across server implementations. These specifications are based on how a server can be compliant with the Relay API client, but can be useful for any client.

保留类型

¥Reserved Types

与此规范兼容的 GraphQL 服务器必须保留某些类型和类型名称以支持一致的对象识别模型。特别是,该规范为以下类型创建了指南:

¥A GraphQL server compatible with this spec must reserve certain types and type names to support the consistent object identification model. In particular, this spec creates guidelines for the following types:

  • 名为 Node 的接口。

    ¥An interface named Node.

  • 根查询类型上的 node 字段。

    ¥The node field on the root query type.

Node 接口

¥Node Interface

服务器必须提供一个名为 Node 的接口。该接口必须包含一个名为 id 的字段,该字段返回非空 ID

¥The server must provide an interface called Node. That interface must include exactly one field, called id that returns a non-null ID.

这个 id 应该是该对象的全局唯一标识符,只要给出这个 id,服务器就应该能够重新获取该对象。

¥This id should be a globally unique identifier for this object, and given just this id, the server should be able to refetch the object.

内省

¥Introspection

正确实现上述接口的服务器将接受以下内省查询,并返回提供的响应:

¥A server that correctly implements the above interface will accept the following introspection query, and return the provided response:

{
  __type(name: "Node") {
    name
    kind
    fields {
      name
      type {
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}

产量

¥yields

{
  "__type": {
    "name": "Node",
    "kind": "INTERFACE",
    "fields": [
      {
        "name": "id",
        "type": {
          "kind": "NON_NULL",
          "ofType": {
            "name": "ID",
            "kind": "SCALAR"
          }
        }
      }
    ]
  }
}

Node 根字段

¥Node root field

服务器必须提供一个名为 node 的根字段,该字段返回 Node 接口。此根字段必须仅采用一个参数,即名为 id 的非空 ID。

¥The server must provide a root field called node that returns the Node interface. This root field must take exactly one argument, a non-null ID named id.

如果查询返回实现 Node 的对象,则当服务器在 Nodeid 字段中返回的值作为 id 参数传递到 node 根字段时,此根字段应该重新获取相同的对象。

¥If a query returns an object that implements Node, then this root field should refetch the identical object when value returned by the server in the Node’s id field is passed as the id parameter to the node root field.

服务器必须尽最大努力获取这些数据,但并不总是可行;例如,服务器可能会返回带有有效 idUser,但是当请求使用 node 根字段重新获取该用户时,用户的数据库可能不可用,或者用户可能已删除其账户。这样的话,查询该字段的结果应该是 null

¥The server must make a best effort to fetch this data, but it may not always be possible; for example, the server may return a User with a valid id, but when the request is made to refetch that user with the node root field, the user’s database may be unavailable, or the user may have deleted their account. In this case, the result of querying this field should be null.

内省

¥Introspection

正确实现上述要求的服务器将接受以下内省查询,并返回包含所提供响应的响应。

¥A server that correctly implements the above requirement will accept the following introspection query, and return a response that contains the provided response.

{
  __schema {
    queryType {
      fields {
        name
        type {
          name
          kind
        }
        args {
          name
          type {
            kind
            ofType {
              name
              kind
            }
          }
        }
      }
    }
  }
}

产量

¥yields

{
  "__schema": {
    "queryType": {
      "fields": [
        // This array may have other entries
        {
          "name": "node",
          "type": {
            "name": "Node",
            "kind": "INTERFACE"
          },
          "args": [
            {
              "name": "id",
              "type": {
                "kind": "NON_NULL",
                "ofType": {
                  "name": "ID",
                  "kind": "SCALAR"
                }
              }
            }
          ]
        }
      ]
    }
  }
}

字段稳定性

¥Field stability

如果查询中出现两个对象,两者都实现具有相同 ID 的 Node,则这两个对象必须相等。

¥If two objects appear in a query, both implementing Node with identical IDs, then the two objects must be equal.

出于此定义的目的,对象相等性定义如下:

¥For the purposes of this definition, object equality is defined as follows:

  • 如果在两个对象上查询某个字段,则在第一个对象上查询该字段的结果必须等于在第二个对象上查询该字段的结果。

    ¥If a field is queried on both objects, the result of querying that field on the first object must be equal to the result of querying that field on the second object.

    • 如果该字段返回标量,则定义适合该标量的相等性。

      ¥If the field returns a scalar, equality is defined as is appropriate for that scalar.

    • 如果字段返回枚举,则相等性定义为两个字段返回相同的枚举值。

      ¥If the field returns an enum, equality is defined as both fields returning the same enum value.

    • 如果该字段返回一个对象,则按照上述递归定义相等性。

      ¥If the field returns an object, equality is defined recursively as per the above.

例如:

¥For example:

{
  fourNode: node(id: "4") {
    id
    ... on User {
      name
      userWithIdOneGreater {
        id
        name
      }
    }
  }
  fiveNode: node(id: "5") {
    id
    ... on User {
      name
      userWithIdOneLess {
        id
        name
      }
    }
  }
}

可能会返回:

¥might return:

{
  "fourNode": {
    "id": "4",
    "name": "Mark Zuckerberg",
    "userWithIdOneGreater": {
      "id": "5",
      "name": "Chris Hughes"
    }
  },
  "fiveNode": {
    "id": "5",
    "name": "Chris Hughes",
    "userWithIdOneLess": {
      "id": "4",
      "name": "Mark Zuckerberg"
    }
  }
}

因为 fourNode.idfiveNode.userWithIdOneLess.id 相同,所以通过上面的条件我们保证 fourNode.name 一定与 fiveNode.userWithIdOneLess.name 相同,事实也确实如此。

¥Because fourNode.id and fiveNode.userWithIdOneLess.id are the same, we are guaranteed by the conditions above that fourNode.name must be the same as fiveNode.userWithIdOneLess.name, and indeed it is.

复数识别根字段

¥Plural identifying root fields

想象一个名为 username 的根字段,它接受用户的用户名并返回相应的用户:

¥Imagine a root field named username, that takes a user’s username and returns the corresponding user:

{
  username(username: "zuck") {
    id
  }
}

可能会返回:

¥might return:

{
  "username": {
    "id": "4"
  }
}

显然,我们可以将响应中的对象(ID 为 4 的用户)与请求链接起来,识别用户名 “zuck” 的对象。现在想象一个名为 usernames 的根字段,它接受用户名列表并返回对象列表:

¥Clearly, we can link up the object in the response, the user with ID 4, with the request, identifying the object with username “zuck”. Now imagine a root field named usernames, that takes a list of usernames and returns a list of objects:

{
  usernames(usernames: ["zuck", "moskov"]) {
    id
  }
}

可能会返回:

¥might return:

{
  "usernames": [
    {
      "id": "4"
    },
    {
      "id": "6"
    }
  ]
}

为了使客户端能够将用户名链接到响应,它需要知道响应中的数组与作为参数传递的数组的大小相同,并且响应中的顺序将与参数中的顺序匹配 。我们将这些复数标识根字段称为复数标识根字段,其要求如下所述。

¥For clients to be able to link the usernames to the responses, it needs to know that the array in the response will be the same size as the array passed as an argument, and that the order in the response will match the order in the argument. We call these plural identifying root fields, and their requirements are described below.

字段

¥Fields

符合此规范的服务器可以公开接受输入参数列表并返回响应列表的根字段。对于符合规范的客户端使用这些字段,这些字段必须是复数标识根字段,并遵守以下要求。

¥A server compliant with this spec may expose root fields that accept a list of input arguments, and returns a list of responses. For spec-compliant clients to use these fields, these fields must be plural identifying root fields, and obey the following requirements.

注:符合规范的服务器可能会公开不是复数标识根字段的根字段;符合规范的客户端将无法在其查询中使用这些字段作为根字段。

¥NOTE Spec-compliant servers may expose root fields that are not plural identifying root fields; the spec-compliant client will just be unable to use those fields as root fields in its queries.

多个标识根字段必须有一个参数。该参数的类型必须是非空值的非空列表。在我们的 usernames 示例中,该字段将采用名为 usernames 的单个参数,其类型(使用我们的类型系统简写)将为 [String!]!

¥Plural identifying root fields must have a single argument. The type of that argument must be a non-null list of non-nulls. In our usernames example, the field would take a single argument named usernames, whose type (using our type system shorthand) would be [String!]!.

复数标识根字段的返回类型必须是列表或列表的非空封装器。该列表必须封装 Node 接口、实现 Node 接口的对象或这些类型的非空封装器。

¥The return type of a plural identifying root field must be a list, or a non-null wrapper around a list. The list must wrap the Node interface, an object that implements the Node interface, or a non-null wrapper around those types.

每当使用复数标识根字段时,响应中列表的长度必须与参数中列表的长度相同。响应中的每个项目必须与其输入中的项目相对应;更正式地说,如果传递根字段输入列表 Lin 会产生输出值 Lout,那么对于任意排列 P,传递根字段 P(Lin) 必定会产生输出值 P(Lout)

¥Whenever the plural identifying root field is used, the length of the list in the response must be the same as the length of the list in the arguments. Each item in the response must correspond to its item in the input; more formally, if passing the root field an input list Lin resulted in output value Lout, then for an arbitrary permutation P, passing the root field P(Lin) must result in output value P(Lout).

因此,建议服务器不要让响应类型封装非空封装器,因为如果无法获取输入中给定条目的对象,它仍然必须在输出中为该输入条目提供一个值 ;null 是一个有用的值。

¥Because of this, servers are advised to not have the response type wrap a non-null wrapper, because if it is unable to fetch the object for a given entry in the input, it still must provide a value in the output for that input entry; null is a useful value for doing so.