Skip to content

Commit baa73cd

Browse files
binaryseedIvanGoncharov
authored andcommitted
Add InputUnion problem statement (#618)
* Add InputUnion problem statement * Apply suggestions from code review Co-Authored-By: Benjie Gillam <[email protected]> * clarify * split problem statement and sketch * add input where needed * note nested inputs * clarify final mutation sketch
1 parent fd5869b commit baa73cd

File tree

1 file changed

+114
-2
lines changed

1 file changed

+114
-2
lines changed

rfcs/InputUnion.md

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,120 @@ To help bring this idea to reality, you can contribute through two channels:
1818

1919
## Problem Statement
2020

21-
TODO:
22-
* [ ] Describe at a high level the problem that Input Union is trying to solve.
21+
GraphQL currently provides polymorphic types that enable schema authors to model complex **Object** types that have multiple shapes while remaining type-safe, but lacks an equivilant capability for **Input** types.
22+
23+
Over the years there have been numerous proposals from the community to add a polymorphic input type. Without such a type, schema authors have resorted to a handful of work-arounds to model their domains. These work-arounds have led to schemas that aren't as expressive as they could be, and schemas where mutations that ideally mirror queries are forced to be modeled differently.
24+
25+
## Problem Sketch
26+
27+
To understand the problem space a little more, we'll sketch out an example that explores a domain from the perspective of a Query and a Mutation. However, it's important to note that the problem is not limited to mutations, since `Input` types are used in field arguments for any GraphQL operation type.
28+
29+
Let's imagine an animal shelter for our example. When querying for a list of the animals, it's easy to see how abstract types are useful - we can get data specific to the type of the animal easily.
30+
31+
```graphql
32+
{
33+
animalShelter(location: "Portland, OR") {
34+
animals {
35+
__typename
36+
name
37+
age
38+
... on Cat { livesLeft }
39+
... on Dog { breed }
40+
... on Snake { venom }
41+
}
42+
}
43+
}
44+
```
45+
46+
However, when we want to submit data, we can't use an `interface` or `union`, so we must model around that.
47+
48+
One technique commonly used to is a **tagged union** pattern. This essentially boils down to a "wrapper" input that isolates each type into it's own field. The field name takes on the convention of representing the type.
49+
50+
```graphql
51+
mutation {
52+
logAnimalDropOff(
53+
location: "Portland, OR"
54+
animals: [
55+
{cat: {name: "Buster", age: 3, livesLeft: 7}}
56+
]
57+
)
58+
}
59+
```
60+
61+
Unfortunately, this opens up a set of problems, since the Tagged union input type actually contains many fields, any of which could be submitted.
62+
63+
```graphql
64+
input AnimalDropOffInput {
65+
cat: CatInput
66+
dog: DogInput
67+
snake: SnakeInput
68+
}
69+
```
70+
71+
This allows non-sensical mutations to pass GraphQL validation, for example representing an animal that is both a `Cat` and a `Dog`.
72+
73+
```graphql
74+
mutation {
75+
logAnimalDropOff(
76+
location: "Portland, OR"
77+
animals: [
78+
{
79+
cat: {name: "Buster", age: 3, livesLeft: 7},
80+
dog: {name: "Ripple", age: 2, breed: WHIPPET}
81+
}
82+
]
83+
)
84+
}
85+
```
86+
87+
In addition, relying on this layer of abstraction means that this domain must be modelled differently across input & output. This can put a larger burden on the developer interacting with the schema, both in terms of lines of code and complexity.
88+
89+
```json
90+
// JSON structure returned from a query
91+
{
92+
"animals": [
93+
{"__typename": "Cat", "name": "Ruby", "age": 2, "livesLeft": 9}
94+
{"__typename": "Snake", "name": "Monty", "age": 13, "venom": "POISON"}
95+
]
96+
}
97+
```
98+
99+
```json
100+
// JSON structure submitted to a mutation
101+
{
102+
"animals": [
103+
{"cat": {"name": "Ruby", "age": 2, "livesLeft": 9}},
104+
{"snake": {"name": "Monty", "age": 13, "venom": "POISON"}}
105+
]
106+
}
107+
```
108+
109+
Another common approach is to provide a unique mutation for every type. A schema employing this technique might have `logCatDropOff`, `logDogDropOff` and `logSnakeDropOff` mutations. This removes the potential for modeling non-sensical situations, but it explodes the number of mutations in a schema, making the schema less accessible. If the type is nested inside other inputs, this approach simply isn't feasable.
110+
111+
These workarounds only get worse at scale. Real world GraphQL schemas can have dozens if not hundreds of possible types for a single `Interface` or `Union`.
112+
113+
The goal of the **Input Union** is to bring a polymorphic type to Inputs. This would enable us to model situations where an input may be of different types in a type-safe and elegant manner, like we can with outputs.
114+
115+
```graphql
116+
mutation {
117+
logAnimalDropOff(
118+
location: "Portland, OR"
119+
120+
# Problem: we need to determine the type of each Animal
121+
animals: [
122+
# This is meant to be a CatInput
123+
{name: "Buster", age: 3, livesLeft: 7},
124+
125+
# This is meant to be a DogInput
126+
{name: "Ripple", age: 2},
127+
]
128+
)
129+
}
130+
```
131+
132+
In this mutation, we encounter the main challenge of the **Input Union** - we need to determine the correct type of the data submitted.
133+
134+
A wide variety of solutions have been explored by the community, and they are outlined in detail in this document under [Possible Solutions](#Possible-Solutions).
23135

24136
## Use Cases
25137

0 commit comments

Comments
 (0)