Skip to content

Commit 15bf14b

Browse files
authored
Merge pull request #12 from graphql-java/merged-fields-blog
merged fields blob post
2 parents 0632942 + 69248ab commit 15bf14b

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
+++
2+
title = "GraphQL Deep Dive - Part 1: merged fields"
3+
author = "Andreas Marek"
4+
tags = []
5+
categories = []
6+
date = 2019-01-22T00:00:00+10:00
7+
+++
8+
9+
# GraphQL Deep Dive series
10+
11+
Welcome to the new series "GraphQL deep dive" where we will explore advanced or unknown GraphQL topics. The plan is to discuss things mostly in a language and implementation neutral way, even if it is hosted on graphql-java.com.
12+
13+
# Merged Fields
14+
15+
First thing we are looking at is "merged fields".
16+
17+
GraphQL allows for a field to be declared multiple times in a query as long as it can be merged.
18+
19+
Valid GraphQL queries are:
20+
21+
{{< highlight Scala "linenos=table" >}}
22+
{
23+
foo
24+
foo
25+
}
26+
{{< / highlight >}}
27+
28+
<p/>
29+
30+
{{< highlight Scala "linenos=table" >}}
31+
{
32+
foo(id: "123")
33+
foo(id: "123")
34+
foo(id: "123")
35+
}
36+
{{< / highlight >}}
37+
<p/>
38+
39+
{{< highlight Scala "linenos=table" >}}
40+
{
41+
foo(id: "123") {
42+
id
43+
}
44+
foo(id: "123") {
45+
name
46+
}
47+
foo(id: "123") {
48+
id
49+
name
50+
}
51+
}
52+
{{< / highlight >}}
53+
54+
Each of these queries will result in a result with just one "foo" key, not two or three.
55+
56+
Invalid Queries are:
57+
58+
{{< highlight Scala "linenos=table" >}}
59+
{
60+
foo
61+
foo(id: "123")
62+
}
63+
{{< / highlight >}}
64+
<p/>
65+
{{< highlight Scala "linenos=table" >}}
66+
{
67+
foo(id: "123")
68+
foo(id: "456", id2: "123")
69+
}
70+
{{< / highlight >}}
71+
<p/>
72+
{{< highlight Scala "linenos=table" >}}
73+
{
74+
foo(id: "123")
75+
foo: foo2
76+
}
77+
{{< / highlight >}}
78+
79+
The reason why they are not valid, is because the fields are different: in the first two examples the arguments differ and the third query actually has two different fields under the same key.
80+
81+
# Motivation
82+
83+
The examples so far don't seem really useful, but it all makes sense when you add fragments:
84+
85+
{{< highlight Scala "linenos=table" >}}
86+
{
87+
...myFragment1
88+
...myFragment2
89+
}
90+
91+
fragment myFragment1 on Query {
92+
foo(id: "123") {
93+
name
94+
}
95+
}
96+
fragment myFragment2 on Query {
97+
foo(id: "123") {
98+
url
99+
}
100+
}
101+
102+
{{< / highlight >}}
103+
104+
<p/>
105+
Fragments are designed to be written by different parties (for example different components in a UI) which should not know anything about each other. Requiring that every field can only be declared once would make this objective unfeasible.
106+
107+
But by allowing fields merging, as long as the fields are the same, allows fragments to be authored in an independent way from each other.
108+
109+
110+
# Rules when fields can be merged
111+
112+
The specific details when fields can be merged are written down in [Field Selection Merging](https://facebook.github.io/graphql/draft/#sec-Field-Selection-Merging) in the spec.
113+
114+
The rules are what you would expect in general and they basically say that fields must be the same. The following examples are taken from the spec and they are all valid:
115+
116+
{{< highlight Scala "linenos=table" >}}
117+
fragment mergeIdenticalFields on Dog {
118+
name
119+
name
120+
}
121+
fragment mergeIdenticalAliasesAndFields on Dog {
122+
otherName: name
123+
otherName: name
124+
}
125+
fragment mergeIdenticalFieldsWithIdenticalArgs on Dog {
126+
doesKnowCommand(dogCommand: SIT)
127+
doesKnowCommand(dogCommand: SIT)
128+
}
129+
fragment mergeIdenticalFieldsWithIdenticalValues on Dog {
130+
doesKnowCommand(dogCommand: $dogCommand)
131+
doesKnowCommand(dogCommand: $dogCommand)
132+
}
133+
{{< / highlight >}}
134+
135+
The most complex case happens when you have fields in fragments on different types:
136+
137+
{{< highlight Scala "linenos=table" >}}
138+
fragment safeDifferingFields on Pet {
139+
... on Dog {
140+
volume: barkVolume
141+
}
142+
... on Cat {
143+
volume: meowVolume
144+
}
145+
}
146+
{{< / highlight >}}
147+
148+
This is normally invalid because `volume` is an alias for two different fields `barkVolume` and `meowVolume` but because only one of the some are actually resolved and they both return a value of the same type (we assume here that `barkVolume` and `meowVolume` are both of the same type) it is valid.
149+
150+
{{< highlight Scala "linenos=table" >}}
151+
fragment safeDifferingArgs on Pet {
152+
... on Dog {
153+
doesKnowCommand(dogCommand: SIT)
154+
}
155+
... on Cat {
156+
doesKnowCommand(catCommand: JUMP)
157+
}
158+
}
159+
{{< / highlight >}}
160+
161+
This is again a valid case because even if the first `doesKnowCommand` has a different argument than the second `doesKnowCommand` only one of them is actually resolved.
162+
163+
In the next example `someValue` has different types (we assume that `nickname` is a `String` and `meowVolume` is a `Int`) and therefore the query is not valid:
164+
165+
{{< highlight Scala "linenos=table" >}}
166+
fragment conflictingDifferingResponses on Pet {
167+
... on Dog {
168+
someValue: nickname
169+
}
170+
... on Cat {
171+
someValue: meowVolume
172+
}
173+
}
174+
{{< / highlight >}}
175+
176+
# Sub selections and directives
177+
178+
One thing to keep in my mind is that the sub selections of fields are merged together. For example here `foo` is resolved once and than `id` and `name` is resolved.
179+
180+
{{< highlight Scala "linenos=table" >}}
181+
{
182+
foo(id: "123") {
183+
id
184+
}
185+
foo(id: "123") {
186+
name
187+
}
188+
}
189+
{{< / highlight >}}
190+
191+
This query is the same as:
192+
193+
{{< highlight Scala "linenos=table" >}}
194+
{
195+
foo(id: "123") {
196+
id
197+
name
198+
}
199+
}
200+
{{< / highlight >}}
201+
202+
The second thing to keep in mind is that different directives can be on each field:
203+
204+
{{< highlight Scala "linenos=table" >}}
205+
{
206+
foo(id: "123") @myDirective {
207+
id
208+
}
209+
foo(id: "123") @myOtherDirective {
210+
name
211+
}
212+
}
213+
{{< / highlight >}}
214+
215+
So if you want to know all directives for the current field you are resolving you actually need to look at all of the merged fields from the query.
216+
217+
# Merged fields in graphql-js and GraphQl Java
218+
219+
In graphql-js merged fields are relevant when you implement a resolver and you need access to the specific ast field of the query. The `info` objects has a property `fieldNodes` which gives you access to all ast fields which are merged together.
220+
221+
In GraphQL Java depending on the version you are running you have `List<Field> getFields()` in the `DataFetcherEnvironment` or for GraphQL Java newer than `12.0` you have also `MergedField getMergedField()` which is the recommend way to access all merged fields.
222+

0 commit comments

Comments
 (0)