open:global-object-identification

Global Object Identification

https://graphql.org/learn/global-object-identification/

일관된 개체 액세스를 통해 간단한 캐싱 및 개체 조회 가능1)

GraphQL 클라이언트가 캐싱 및 데이터 다시 가져오기를 우아하게 처리할 수 있는 옵션을 제공하려면 GraphQL 서버는 표준화된 방식으로 개체 식별자를 노출해야 합니다.2)

이것이 작동하려면 클라이언트가 ID로 개체를 요청하는 표준 메커니즘을 통해 쿼리해야 합니다. 그런 다음 응답에서 스키마는 이러한 ID를 제공하는 표준 방법을 제공해야 합니다.3)

개체에 대해 ID 외에 알려진 것이 거의 없기 때문에 이러한 개체를 “노드”라고 합니다. 다음은 노드에 대한 쿼리의 예입니다.4)

{
  node(id: "4") {
    id
    ... on User {
      name
    }
  }

  • GraphQL 스키마는 루트 쿼리 개체의 노드 필드를 통해 모든 개체를 가져올 수 있도록 형식이 지정됩니다. 이것은 “Node” 인터페이스를 준수하는 객체를 반환합니다.5)
  • id 필드는 응답에서 안전하게 추출할 수 있으며 캐싱 및 다시 가져오기를 통해 재사용을 위해 저장할 수 있습니다.6)
  • 클라이언트는 인터페이스 조각을 사용하여 노드 인터페이스를 준수하는 유형에 특정한 추가 정보를 추출할 수 있습니다. 이 경우에는 “User” 입니다.7)

노드 인터페이스는 다음과 같습니다.8)

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

사용자가 다음을 통해 준수하는 경우:9)

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

아래의 모든 내용은 서버 구현 간의 일관성을 보장하기 위해 개체 식별에 대한 사양을 보다 공식적인 요구 사항과 함께 설명합니다. 이러한 사양은 서버가 릴레이 API 클라이언트와 호환되는 방법을 기반으로 하지만 모든 클라이언트에 유용할 수 있습니다.10)

이 사양과 호환되는 GraphQL 서버는 일관된 개체 식별 모델을 지원하기 위해 특정 유형 및 유형 이름을 예약해야 합니다. 특히 이 사양은 다음 유형에 대한 지침을 만듭니다.11)

  • Node라는 인터페이스입니다.12)
  • 루트 쿼리 유형의 node 필드입니다.13)

서버는 Node라는 인터페이스를 제공해야 합니다. 해당 인터페이스는 null이 아닌 ID를 반환하는 id라는 필드를 정확히 하나만 포함해야 합니다.14)

이 ID는 이 개체에 대한 전역적으로 고유한 식별자여야 하며 이 ID만 주어지면 서버가 개체를 다시 가져올 수 있어야 합니다.15)

위의 인터페이스를 올바르게 구현하는 서버는 다음 자체 검사 쿼리를 수락하고 제공된 응답을 반환합니다.16)

{
  __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"
          }
        }
      }
    ]
  }
}

서버는 노드 인터페이스를 반환하는 노드라는 루트 필드를 제공해야 합니다. 이 루트 필드는 id라는 null이 아닌 ID인 정확히 하나의 인수를 취해야 합니다.17)

쿼리가 노드를 구현하는 개체를 반환하는 경우 노드의 id 필드에 서버가 반환한 값이 노드 루트 필드에 id 매개변수로 전달될 때 이 루트 필드는 동일한 개체를 다시 가져와야 합니다.18)

서버는 이 데이터를 가져오기 위해 최선을 다해야 하지만 항상 가능한 것은 아닙니다. 예를 들어, 서버는 유효한 ID를 가진 사용자를 반환할 수 있지만 노드 루트 필드로 해당 사용자를 다시 가져오도록 요청하면 사용자의 데이터베이스를 사용할 수 없거나 사용자가 계정을 삭제했을 수 있습니다. 이 경우 이 필드를 쿼리한 결과는 null이어야 합니다.19)

위의 요구 사항을 올바르게 구현하는 서버는 다음 자체 검사 쿼리를 수락하고 제공된 응답이 포함된 응답을 반환합니다.20)

{
  __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"
                }
              }
            }
          ]
        }
      ]
    }
  }
}

동일한 ID로 Node를 구현하는 두 개체가 쿼리에 나타나면 두 개체가 같아야 합니다.21)

이 정의의 목적을 위해 객체 평등은 다음과 같이 정의됩니다.22)

  • 두 개체에서 필드를 쿼리하는 경우 첫 번째 개체에서 해당 필드를 쿼리한 결과는 두 번째 개체에서 해당 필드를 쿼리한 결과와 같아야 합니다.23)
    • 필드가 스칼라를 반환하는 경우 같음은 해당 스칼라에 적합한 것으로 정의됩니다.24)
    • 필드가 열거형을 반환하는 경우 같음은 동일한 열거형 값을 반환하는 두 필드로 정의됩니다.25)
    • 필드가 객체를 반환하면 같음은 위와 같이 재귀적으로 정의됩니다.26)

예를 들어:27)

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

다음을 반환할 수 있습니다.28)

{
  "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과 동일해야 하고 실제로 동일해야 한다는 위의 조건에 의해 보장됩니다.29)

사용자 이름을 가져와 해당 사용자를 반환하는 username이라는 루트 필드를 상상해 보십시오.30)

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

다음을 반환할 수 있습니다.31)

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

분명히, 우리는 ID 4를 가진 사용자인 응답의 객체를 사용자 이름 “zuck”으로 식별하는 요청과 연결할 수 있습니다. 이제 사용자 이름 목록을 가져와 객체 목록을 반환하는 사용자 이름이라는 루트 필드를 상상해 보십시오.32)

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

다음을 반환할 수 있습니다.33)

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

클라이언트가 사용자 이름을 응답에 연결할 수 있으려면 응답의 배열이 인수로 전달된 배열과 크기가 같고 응답의 순서가 인수의 순서와 일치해야 함을 알아야 합니다. . 우리는 이러한 복수의 식별 루트 필드를 호출하며 해당 요구 사항은 아래에 설명되어 있습니다.34)

이 사양을 준수하는 서버는 입력 인수 목록을 수락하고 응답 목록을 반환하는 루트 필드를 노출할 수 있습니다. 사양 준수 클라이언트가 이러한 필드를 사용하려면 이러한 필드가 루트 필드를 식별하는 복수이어야 하며 다음 요구 사항을 준수해야 합니다.35)

참고 사양 준수 서버는 루트 필드를 식별하는 복수가 아닌 루트 필드를 노출할 수 있습니다. 사양 준수 클라이언트는 해당 필드를 쿼리의 루트 필드로 사용할 수 없습니다.36)

복수의 식별 루트 필드에는 단일 인수가 있어야 합니다. 해당 인수의 유형은 null이 아닌 null이 아닌 목록이어야 합니다. 사용자 이름 예제에서 필드는 사용자 이름이라는 단일 인수를 사용하며, 유형(유형 시스템 약칭 사용)은 [String!]!이 됩니다.37)

복수 식별 루트 필드의 반환 유형은 목록이거나 목록 주위의 null이 아닌 래퍼여야 합니다. 목록은 Node 인터페이스, Node 인터페이스를 구현하는 객체 또는 이러한 유형 주위의 null이 아닌 래퍼를 래핑해야 합니다.38)

복수 식별 루트 필드가 사용될 때마다 응답의 목록 길이는 인수의 목록 길이와 같아야 합니다. 응답의 각 항목은 입력의 항목과 일치해야 합니다. 더 공식적으로, 루트 필드에 입력 목록 Lin을 전달하면 출력 값 Lout이 생성되고 임의의 순열 P에 대해 루트 필드 P(Lin)을 전달하면 출력 값 P(Lout)이 생성되어야 합니다.39)

이 때문에 서버는 응답 유형이 null이 아닌 래퍼를 래핑하지 않는 것이 좋습니다. 입력에서 주어진 항목에 대한 개체를 가져올 수 없는 경우에도 해당 입력 항목에 대한 출력에 값을 제공해야 하기 때문입니다. ; null은 그렇게 하는 데 유용한 값입니다.40)


1)
Consistent object access enables simple caching and object lookups
2)
To provide options for GraphQL clients to elegantly handle caching and data refetching, GraphQL servers need to expose object identifiers in a standardized way.
3)
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.
4)
Because little is known about the object other than its ID, we call these objects “nodes.” Here is an example query for a node:
5)
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.
6)
The id field can be extracted out of the response safely, and can be stored for re-use via caching and refetching.
7)
Clients can use interface fragments to extract additional information specific to the type which conform to the node interface. In this case a “User”.
8)
The Node interface looks like:
9)
With a User conforming via:
10)
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.
11)
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:
12)
An interface named Node.
13)
The node field on the root query type.
14)
The server must provide an interface called Node. That interface must include exactly one field, called id that returns a non-null ID.
15)
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.
16)
A server that correctly implements the above interface will accept the following introspection query, and return the provided response:
17)
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.
18)
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.
19)
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.
20)
A server that correctly implements the above requirement will accept the following introspection query, and return a response that contains the provided response.
21)
If two objects appear in a query, both implementing Node with identical IDs, then the two objects must be equal.
22)
For the purposes of this definition, object equality is defined as follows:
23)
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.
24)
If the field returns a scalar, equality is defined as is appropriate for that scalar.
25)
If the field returns an enum, equality is defined as both fields returning the same enum value.
26)
If the field returns an object, equality is defined recursively as per the above.
27)
For example:
28) , 31) , 33)
might return:
29)
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.
30)
Imagine a root field named username, that takes a user's username and returns the corresponding user:
32)
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:
34)
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.
35)
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.
36)
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.
37)
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!]!.
38)
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.
39)
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).
40)
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.
  • open/global-object-identification.txt
  • 마지막으로 수정됨: 2022/09/05 05:50
  • 저자 127.0.0.1