Skip to content

Commit 72438bf

Browse files
committed
Add Schema Coordinates RFC
1 parent c976d31 commit 72438bf

File tree

3 files changed

+542
-0
lines changed

3 files changed

+542
-0
lines changed

rfcs/SchemaCoordinates.md

Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
# RFC: Schema Coordinates
2+
3+
**Proposed by:** [Mark Larah](https://twitter.com/mark_larah) - Yelp
4+
5+
This RFC proposes formalizing "Schema Coordinates" - a human readable syntax to
6+
uniquely identify a type, field, or field argument defined in a GraphQL Schema.
7+
8+
This should be listed as a non-normative note in the GraphQL specification to
9+
serve as an official reference for use by third party tooling.
10+
11+
## 📜 Problem Statement
12+
13+
Third party GraphQL tooling and libraries may wish to refer to a field, or set of
14+
fields in a schema. Use cases include documentation, metrics and logging
15+
libraries.
16+
17+
![](https://i.fluffy.cc/5Cz9cpwLVsH1FsSF9VPVLwXvwrGpNh7q.png)
18+
19+
_(Example shown from GraphiQL's documentation search tab)_
20+
21+
There already exists a convention used by some third party libraries for writing
22+
out fields in a unique way for such purposes. However, there is no formal
23+
specification or name for this convention.
24+
25+
### Use cases
26+
27+
1. A GraphQL server wants to **log how often each field in the schema is
28+
requested**. This may be implemented by incrementing a counter by the name of
29+
the schema coordinate for each field executed in a request.
30+
31+
_Existing implementations: Yelp (internal), Facebook (internal)_
32+
33+
1. GraphiQL and other playgrounds / documentation sites want to show a list of
34+
**search results** when a user searches for a type or field name. We can
35+
display a list of schema coordinates that match the search term. A schema
36+
coordinate can also be used in the hyperlink to form a permalink for
37+
documentation for a particular field.
38+
39+
_Existing implementations: GraphiQL, Apollo Studio (see "Prior Art")_
40+
41+
1. A developer may want to perform **analytics** on all known
42+
[persisted queries][apq] - e.g. what are the most commonly used fields across
43+
all documents. Schema coordinates may be used as the index/lookup keys when
44+
storing this information in the database.
45+
46+
_Existing implementations: Yelp (internal)_
47+
48+
[apq]: https://www.apollographql.com/docs/apollo-server/performance/apq/
49+
50+
1. A **GitHub bot** may want to warn developers in a Pull Request comment
51+
whenever the schema diff contains a breaking change. Schema selectors can be
52+
used to provide a list of which fields were broken.
53+
54+
_Existing implementations: GraphQL Inspector (see "Prior Art")_
55+
56+
1. **GraphQL IDEs** (e.g. GraphiQL, GraphQL Playground, Apollo Studio) may wish
57+
to display the schema definition type of a node in a query when hovering over
58+
it.
59+
60+
<details>
61+
<summary>Example</summary>
62+
![](https://i.fluffy.cc/g78sJCjCJ0MsbNPhvgPXP46Kh9knBCKF.png)
63+
</details>
64+
65+
Schema coordinates can be used to form the left hand side of this popover.
66+
67+
_Existing implementations: Apollo Studio (see "Prior Art")_
68+
69+
## 🚫 What this RFC does _not_ propose
70+
71+
- This does not cover "selectors" or "wildcard" syntax - e.g. `User.*`. _(See
72+
alternatives considered.)_
73+
- There are **no proposed GraphQL language/syntax changes**
74+
- There are **no proposed GraphQL runtime changes**
75+
- [Schema coordinate non-goals](#-syntax-non-goals)
76+
77+
## ✨ Worked Examples
78+
79+
For example, consider the following schema:
80+
81+
```graphql
82+
type Person {
83+
name: String
84+
}
85+
86+
type Business {
87+
name: String
88+
owner: Person
89+
}
90+
91+
type Query {
92+
searchBusinesses(name: String): [Business]
93+
}
94+
```
95+
96+
We can write the following list of Schema Coordinates:
97+
98+
- `Person` uniquely identifies the the "Person" type
99+
- `Business` uniquely identifies the the "Business" type
100+
- `Person.name` uniquely identifies the "name" field on the "Person" type
101+
- `Business.name` uniquely identifies the "name" field on the "Business"
102+
type
103+
- `Business.owner` uniquely identifies the "owner" field on the "Business" type
104+
- `Query.searchBusinesses` uniquely identifies the "searchBusinesses" field on
105+
the "Query" type
106+
- `Query.searchBusinesses(name)` uniquely identifies the "name" argument on the
107+
"searchBusinesses" field on the "Query" type
108+
109+
This RFC standardizes how we write coordinates GraphQL Schema members as above.
110+
111+
## 🎨 Prior art
112+
113+
- The name "schema coordinates" is inspired from [GraphQL Java](https://github.com/graphql-java/graphql-java)
114+
(4.3k stars), where "field coordinates" are already used in a similar way as
115+
described in this RFC.
116+
117+
- [GitHub comment](https://github.com/graphql/graphql-spec/issues/735#issuecomment-646979049)
118+
- [Implementation](https://github.com/graphql-java/graphql-java/blob/2acb557474ca73/src/main/java/graphql/schema/FieldCoordinates.java)
119+
120+
- GraphiQL displays schema coordinates in its documentation search tab:
121+
122+
![](https://i.fluffy.cc/5Cz9cpwLVsH1FsSF9VPVLwXvwrGpNh7q.png)
123+
124+
- [GraphQL Inspector](https://github.com/kamilkisiela/graphql-inspector) (840
125+
stars) shows schema coordinates in its output:
126+
127+
![](https://i.imgur.com/HAf18rz.png)
128+
129+
- [Apollo Studio](https://www.apollographql.com/docs/studio/) shows schema
130+
coordinates when hovering over fields in a query:
131+
132+
![](https://i.fluffy.cc/g78sJCjCJ0MsbNPhvgPXP46Kh9knBCKF.png)
133+
134+
## 🥣 Document -> Schema Coordinate serialization
135+
136+
Use cases 3 and 5 above imply that a mapping from GraphQL query nodes to schema
137+
coordinates is performed.
138+
139+
For example, consider the following schema:
140+
141+
```graphql
142+
type Person {
143+
name: String
144+
}
145+
146+
type Business {
147+
name: String
148+
owner: Person
149+
}
150+
151+
type Query {
152+
searchBusiness(name: String): [Business]
153+
}
154+
```
155+
156+
And the following query:
157+
158+
```graphql
159+
query {
160+
searchBusinesses(name: "El Greco Deli") {
161+
name
162+
owner {
163+
name
164+
}
165+
}
166+
}
167+
```
168+
169+
From the query above, we may calculate the following list of schema coordinates:
170+
171+
- `Query.searchBusinesses`
172+
- `Business.name`
173+
- `Business.owner`
174+
- `Person.name`
175+
176+
_`Query.searchBusinesses(name)` is also a valid member of the output set. The
177+
serialization algorithm may optionally choose to output all permutations of field
178+
arguments used, should this be specified._
179+
180+
A library has been written to demonstrate this mapping: https://github.com/sharkcore/extract-schema-coordinates.
181+
182+
## 🗳️ Alternatives considered
183+
184+
### Naming
185+
186+
- **"Schema Selectors"**
187+
188+
"Selectors" is a term used in [HTML](https://www.w3.org/TR/selectors-api/) and
189+
[CSS](https://drafts.csswg.org/selectors-4/) to _select_ parts of an HTML
190+
document.
191+
192+
This would be a compelling, familiar choice - however, we've decided to not
193+
support wildcard expansion in this spec. See the section
194+
[Syntax Non-goals](#-syntax-non-goals).
195+
196+
- **"type/field pairs"**
197+
198+
This was the original working name. However, there already exists more
199+
established terminology for this concept, and we also wish to describe more
200+
than just types on fields.
201+
202+
- **"Field Coordinates"**
203+
204+
"Field Coordinates" is already understood and used by the popular
205+
[GraphQL Java](https://github.com/graphql-java/graphql-java) project.
206+
207+
[Feedback in the August GraphQL Working Group meeting](https://youtu.be/FYF15RA9H3k?t=3786)
208+
hinted that since we're targeting also describing arguments, _field_
209+
coordinates might not be the right name. Hence "Schema Coordinates" is chosen
210+
instead, as a more generalized form of this.
211+
212+
- **"GraphQL Coordinates"**
213+
214+
Similar to Field Coordinates/Schema Coordinates - however, "GraphQL
215+
Coordinates" is potentially ambiguous as to if it describes _schema_ members,
216+
_query/document_ members or response object members.
217+
218+
- **"Field path" / "GraphQL path"**
219+
220+
[`path` exists as an attribute on `GraphQLResolveInfo`](https://github.com/graphql/graphql-js/blob/8f3d09b54260565/src/type/definition.js#L951).
221+
222+
Given the following query:
223+
224+
```graphql
225+
query {
226+
searchBusinesses(name: "El Greco Deli") {
227+
name
228+
owner {
229+
name
230+
}
231+
}
232+
}
233+
```
234+
235+
`Person.name` in the response may be written as the following "field path":
236+
237+
```json
238+
["query", "searchBusinesses", 1, "owner", "name"]
239+
```
240+
241+
Note that here, the "path" is a serialized _response_ tree traversal, instead
242+
of describing the location of the field in the _schema_.
243+
244+
Since "path" is already used in GraphQL nomenclature to describe the location
245+
of a field in a response, we'll avoid overloading this term.
246+
247+
### Separator
248+
249+
This RFC proposes using "`.`" as the separator character between a type and
250+
field. The following have also been proposed:
251+
252+
- `Foo::bar`
253+
- `Foo#bar`
254+
- `Foo->bar`
255+
- `Foo~bar`
256+
- `Foo:bar`
257+
258+
"`.`" is already used in the existing implementations of field coordinates, hence
259+
the suggested usage in this RFC. However, we may wish to consider one of the
260+
alternatives above, should this conflict with existing or planned language
261+
features.
262+
263+
## 🙅 Syntax Non-goals
264+
265+
This syntax consciously does not cover the following use cases:
266+
267+
- **Wildcard selectors**
268+
269+
Those familiar with `document.querySelector` may be expecting the ability to
270+
pass "wildcards" or "star syntax" to be able to select multiple schema
271+
elements. This implies multiple ways of _selecting_ a schema node.
272+
273+
For example, `User.address` and `User.a*` might both resolve to `User.address`.
274+
But `User.a*` could also ambiguously refer to `User.age`.
275+
276+
It's unclear how wildcard expansion would work with respect to field
277+
arguments\*, potentially violating the requirement of this schema to _uniquely_
278+
identify schema components.
279+
280+
\* _(e.g. does `Query.getUser` also select all arguments on the `getUser`
281+
field? Who knows! A discussion for another time.)_
282+
283+
A more general purpose schema selector language could be built on top of this
284+
spec - however, we'll consider this **out of scope** for now.
285+
286+
- **Directive applications**
287+
288+
This spec does not support selecting applications of directive.
289+
290+
For example:
291+
292+
```graphql
293+
directive @private(scope: String!) on FIELD
294+
295+
type User {
296+
name: String
297+
reviewCount: Int
298+
friends: [User]
299+
email: String @private(scope: 'loggedIn')
300+
}
301+
```
302+
303+
You _can_ select the definition of the `private` directive and its arguments
304+
(with `@private` and `@private(scope)` respectively), but you cannot select the
305+
application of the `@private` on `User.email`.
306+
307+
For the stated use cases of this RFC, it is more likely that consumers want to
308+
select and track usage and changes to the definition of the custom directive
309+
instead.
310+
311+
If we _did_ want to support this, a syntax such as `User.email@private[0]`
312+
could work. (The indexing is necessary since [multiple applications of the same
313+
directive is allowed][multiple-directives], and each is considered unique.)
314+
315+
[multiple-directives]: http://spec.graphql.org/draft/#sec-Directives-Are-Unique-Per-Location
316+
317+
- **Union members**
318+
319+
This spec does not support selecting members inside a union definition.
320+
321+
For example:
322+
323+
```graphql
324+
type Breakfast {
325+
eggCount: Int
326+
}
327+
328+
type Lunch {
329+
sandwichFilling: String
330+
}
331+
332+
union Meal = Breakfast | Lunch
333+
```
334+
335+
You may select the `Meal` definition (as "`Meal`"), but you may **not** select
336+
members on `Meal` (e.g. `Meal.Breakfast` or `Meal.Lunch`).
337+
338+
It is unclear what the use case for this would be, so we won't (yet?) support
339+
this. In such cases, consumers may select type members directly (e.g. `Lunch`).
340+
341+
## 🤔 Drawbacks / Open questions
342+
343+
- https://github.com/graphql/graphql-spec/issues/735 discusses potential
344+
conflicts with the upcoming namespaces proposal - would like to seek clarity on
345+
this
346+
347+
- Should we specify an algorithm for doing the query -> set of schema
348+
coordinates? Or just hint/imply that this mapping theoretically exists? Is this
349+
out of scope?
350+
351+
### Answered questions
352+
353+
- **Is this extensible enough?** The above issue discusses adding arguments as
354+
part of this specification - we haven't touched on this here in order to keep
355+
this RFC small, but we may wish to consider this in the future (e.g.
356+
`Query.searchBusiness:name`).
357+
358+
_Update:_ As discussed in the [August Working Group Meeting][notes], this RFC
359+
now includes the ability to select field arguments
360+
361+
[notes]: https://github.com/graphql/graphql-wg/blob/master/notes/2020-08-06.md#field-coordinates-rfc-15m-mark
362+
363+
- **Would we want to add a method to graphql-js?** A `fieldCoordinateToFieldNode`
364+
method (for example) may take in a field coordinate string and return a field
365+
AST node to serve as a helper / reference implementation of the algorithm to
366+
look up the field node.
367+
368+
_Update:_ [This was discussed in the August Working Group Meeting][meeting] -
369+
it was suggested to keep any utilities as third party libraries to avoid edge
370+
ambiguity problems, and to be able to iterate faster.
371+
372+
[meeting]: https://youtu.be/FYF15RA9H3k?t=2865

0 commit comments

Comments
 (0)