Skip to content

Commit c6fa522

Browse files
authored
Merge pull request graphql#666 from orta/add_objectidentification
Adds the object identification docs into the GraphQL best practices section
2 parents 0e18f28 + 1933eb1 commit c6fa522

File tree

4 files changed

+375
-9
lines changed

4 files changed

+375
-9
lines changed

site/learn/BestPractice-Caching.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ One possible pattern for this is reserving a field, like `id`, to be a globally
3333

3434
This is a powerful tool to hand to client developers. In the same way that the URLs of a resource-based API provided a globally unique key, the `id` field in this system provides a globally unique key.
3535

36-
If the backend uses something like UUIDs for identifiers, then exposing this globally unique ID may be very straightforward! If the backend doesn't have a globally unique ID for every object already, the GraphQL layer might have to construct this. Oftentimes, that's as simple as appending the name of the type to the ID and using that as the identifier; the server might then make that ID opaque by base64-encoding it.
36+
If the backend uses something like UUIDs for identifiers, then exposing this globally unique ID may be very straightforward! If the backend doesn't have a globally unique ID for every object already, the GraphQL layer might have to construct this. Oftentimes, that's as simple as appending the name of the type to the ID and using that as the identifier; the server might then make that ID opaque by base64-encoding it.
37+
38+
Optionally, this ID can then be used to work with the [Global Object Identification](learn/global-object-identification/)'s `node` pattern.
3739

3840
## Compatibility with existing APIs
3941

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
---
2+
title: Global Object Identification
3+
layout: ../_core/DocsLayout
4+
category: Best Practices
5+
permalink: /learn/global-object-identification/
6+
next: /learn/caching/
7+
---
8+
9+
> Consistent object access enables simple caching and object lookups
10+
11+
To provide options for GraphQL clients to elegantly handle caching and data
12+
refetching, GraphQL servers need to expose object identifiers in a standardized
13+
way.
14+
15+
For this to work, a client will need to query via a standard mechanism to
16+
request an object by ID. Then, in the response, the schema will need to provide a
17+
standard way of providing these IDs.
18+
19+
Because little is known about the object other than its ID, we call these
20+
objects "nodes." Here is an example query for a node:
21+
22+
```graphql
23+
{
24+
node(id: "4") {
25+
id
26+
... on User {
27+
name
28+
}
29+
}
30+
```
31+
32+
- The GraphQL schema is formatted to allow fetching any object via the `node` field on the root query object. This
33+
returns objects which conform to a "Node" [interface](/learn/schema/#interfaces).
34+
- The `id` field can be extracted out of the response safely, and can be stored for re-use via caching and refetching.
35+
- Clients can use interface fragments to extract additional information specific to the type which conform to the node interface. In this case a "User".
36+
37+
The Node interface looks like:
38+
39+
```graphql
40+
# An object with a Globally Unique ID
41+
interface Node {
42+
# The ID of the object.
43+
id: ID!
44+
}
45+
```
46+
47+
With a User conforming via:
48+
49+
```graphql
50+
type User implements Node {
51+
id: ID!
52+
# Full name
53+
name: String!
54+
}
55+
```
56+
57+
# Specification
58+
59+
Everything below describes with more formal requirements a specification around object
60+
identification in order to conform to ensure consistency across server implementations. These
61+
specifications are based on how a server can be compliant with the [Relay][relay] API client, but
62+
can be useful for any client.
63+
64+
# Reserved Types
65+
66+
A GraphQL server compatible with this spec must reserve certain types and type names
67+
to support the consistent object identification model. In particular, this spec creates
68+
guidelines for the following types:
69+
70+
- An interface named `Node`.
71+
- The `node` field on the root query type.
72+
73+
# Node Interface
74+
75+
The server must provide an interface called `Node`. That interface
76+
must include exactly one field, called `id` that returns a non-null `ID`.
77+
78+
This `id` should be a globally unique identifier for this object, and given
79+
just this `id`, the server should be able to refetch the object.
80+
81+
## Introspection
82+
83+
A server that correctly implements the above interface will accept the following
84+
introspection query, and return the provided response:
85+
86+
```graphql
87+
{
88+
__type(name: "Node") {
89+
name
90+
kind
91+
fields {
92+
name
93+
type {
94+
kind
95+
ofType {
96+
name
97+
kind
98+
}
99+
}
100+
}
101+
}
102+
}
103+
```
104+
105+
yields
106+
107+
```json
108+
{
109+
"__type": {
110+
"name": "Node",
111+
"kind": "INTERFACE",
112+
"fields": [
113+
{
114+
"name": "id",
115+
"type": {
116+
"kind": "NON_NULL",
117+
"ofType": {
118+
"name": "ID",
119+
"kind": "SCALAR"
120+
}
121+
}
122+
}
123+
]
124+
}
125+
}
126+
```
127+
128+
# Node root field
129+
130+
The server must provide a root field called `node` that returns the `Node`
131+
interface. This root field must take exactly one argument, a non-null ID
132+
named `id`.
133+
134+
If a query returns an object that implements `Node`, then this root field
135+
should refetch the identical object when value returned by the server in the
136+
`Node`'s `id` field is passed as the `id` parameter to the `node` root field.
137+
138+
The server must make a best effort to fetch this data, but it may not always
139+
be possible; for example, the server may return a `User` with a valid `id`,
140+
but when the request is made to refetch that user with the `node` root field,
141+
the user's database may be unavailable, or the user may have deleted their
142+
account. In this case, the result of querying this field should be `null`.
143+
144+
## Introspection
145+
146+
A server that correctly implements the above requirement will accept the
147+
following introspection query, and return a response that contains the
148+
provided response.
149+
150+
```graphql
151+
{
152+
__schema {
153+
queryType {
154+
fields {
155+
name
156+
type {
157+
name
158+
kind
159+
}
160+
args {
161+
name
162+
type {
163+
kind
164+
ofType {
165+
name
166+
kind
167+
}
168+
}
169+
}
170+
}
171+
}
172+
}
173+
}
174+
```
175+
176+
yields
177+
178+
```json
179+
{
180+
"__schema": {
181+
"queryType": {
182+
"fields": [
183+
// This array may have other entries
184+
{
185+
"name": "node",
186+
"type": {
187+
"name": "Node",
188+
"kind": "INTERFACE"
189+
},
190+
"args": [
191+
{
192+
"name": "id",
193+
"type": {
194+
"kind": "NON_NULL",
195+
"ofType": {
196+
"name": "ID",
197+
"kind": "SCALAR"
198+
}
199+
}
200+
}
201+
]
202+
}
203+
]
204+
}
205+
}
206+
}
207+
```
208+
209+
# Field stability
210+
211+
If two objects appear in a query, both implementing `Node` with identical
212+
IDs, then the two objects must be equal.
213+
214+
For the purposes of this definition, object equality is defined as follows:
215+
216+
- If a field is queried on both objects, the result of querying that field on
217+
the first object must be equal to the result of querying that field on the
218+
second object.
219+
- If the field returns a scalar, equality is defined as is appropriate for
220+
that scalar.
221+
- If the field returns an enum, equality is defined as both fields returning
222+
the same enum value.
223+
- If the field returns an object, equality is defined recursively as per the
224+
above.
225+
226+
For example:
227+
228+
```graphql
229+
{
230+
fourNode: node(id: "4") {
231+
id
232+
... on User {
233+
name
234+
userWithIdOneGreater {
235+
id
236+
name
237+
}
238+
}
239+
}
240+
fiveNode: node(id: "5") {
241+
id
242+
... on User {
243+
name
244+
userWithIdOneLess {
245+
id
246+
name
247+
}
248+
}
249+
}
250+
}
251+
```
252+
253+
might return:
254+
255+
```json
256+
{
257+
"fourNode": {
258+
"id": "4",
259+
"name": "Mark Zuckerberg",
260+
"userWithIdOneGreater": {
261+
"id": "5",
262+
"name": "Chris Hughes"
263+
}
264+
},
265+
"fiveNode": {
266+
"id": "5",
267+
"name": "Chris Hughes",
268+
"userWithIdOneLess": {
269+
"id": "4",
270+
"name": "Mark Zuckerberg",
271+
}
272+
}
273+
}
274+
```
275+
276+
Because `fourNode.id` and `fiveNode.userWithIdOneLess.id` are the same, we are
277+
guaranteed by the conditions above that `fourNode.name` must be the same as
278+
`fiveNode.userWithIdOneLess.name`, and indeed it is.
279+
280+
# Plural identifying root fields
281+
282+
Imagine a root field named `username`, that takes a user's username and
283+
returns the corresponding user:
284+
285+
```graphql
286+
{
287+
username(username: "zuck") {
288+
id
289+
}
290+
}
291+
```
292+
293+
might return:
294+
295+
```json
296+
{
297+
"username": {
298+
"id": "4",
299+
}
300+
}
301+
```
302+
303+
Clearly, we can link up the object in the response, the user with ID 4,
304+
with the request, identifying the object with username "zuck". Now imagine a
305+
root field named `usernames`, that takes a list of usernames and returns a
306+
list of objects:
307+
308+
309+
```graphql
310+
{
311+
usernames(usernames: ["zuck", "moskov"]) {
312+
id
313+
}
314+
}
315+
```
316+
317+
might return:
318+
319+
```json
320+
{
321+
"usernames": [
322+
{
323+
"id": "4",
324+
},
325+
{
326+
"id": "6"
327+
}
328+
]
329+
}
330+
```
331+
332+
For clients to be able to link the usernames to the responses, it needs to
333+
know that the array in the response will be the same size as the array
334+
passed as an argument, and that the order in the response will match the
335+
order in the argument. We call these *plural identifying root fields*, and
336+
their requirements are described below.
337+
338+
## Fields
339+
340+
A server compliant with this spec may expose root fields that accept a list of input
341+
arguments, and returns a list of responses. For spec-compliant clients to use these fields,
342+
these fields must be *plural identifying root fields*, and obey the following
343+
requirements.
344+
345+
NOTE Spec-compliant servers may expose root fields that are not *plural
346+
identifying root fields*; the spec-compliant client will just be unable to use those
347+
fields as root fields in its queries.
348+
349+
*Plural identifying root fields* must have a single argument. The type of that
350+
argument must be a non-null list of non-nulls. In our `usernames` example, the
351+
field would take a single argument named `usernames`, whose type (using our type
352+
system shorthand) would be `[String!]!`.
353+
354+
The return type of a *plural identifying root field* must be a list, or a
355+
non-null wrapper around a list. The list must wrap the `Node` interface, an
356+
object that implements the `Node` interface, or a non-null wrapper around
357+
those types.
358+
359+
Whenever the *plural identifying root field* is used, the length of the
360+
list in the response must be the same as the length of the list in the
361+
arguments. Each item in the response must correspond to its item in the input;
362+
more formally, if passing the root field an input list `Lin` resulted in output
363+
value `Lout`, then for an arbitrary permutation `P`, passing the root field
364+
`P(Lin)` must result in output value `P(Lout)`.
365+
366+
Because of this, servers are advised to not have the response type
367+
wrap a non-null wrapper, because if it is unable to fetch the object for
368+
a given entry in the input, it still must provide a value in the output
369+
for that input entry; `null` is a useful value for doing so.
370+
371+
[relay]: https://facebook.github.io/relay/

site/learn/BestPractice-Pagination.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: Pagination
33
layout: ../_core/DocsLayout
44
category: Best Practices
55
permalink: /learn/pagination/
6-
next: /learn/caching/
6+
next: /learn/global-object-identification/
77
---
88

99
> Different pagination models enable different client capabilities

0 commit comments

Comments
 (0)