全局对象识别
¥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
的对象,则当服务器在 Node
的 id
字段中返回的值作为 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.
服务器必须尽最大努力获取这些数据,但并不总是可行;例如,服务器可能会返回带有有效 id
的 User
,但是当请求使用 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.id
和 fiveNode.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.