apollo-kotlin
apollo-kotlin copied to clipboard
Query Field Flattening
Is your feature request related to a problem? Please describe. Hello! We spoke on the Apollo GraphQL mobile stream about this issue. It was a pleasure getting to talk with the squad:)
One of the issues I've run into with GraphQL is that sometimes the data that I need is very deeply nested into a query, and to get that data, I need to write code on the client side to make the code understandable. For example, let's say I have a Forest
GraphQL API, and I want to access the fruits from a specific tree. The issue is that the query would be set up something like this:
query listTrees($treeInput: TreeInput) {
listTrees(input: $treeInput) {
treeEdges: edges {
id
branches {
branchEdges: edges {
id
stems {
stemEdges: edges {
id
fruit {
fruitEdges: edges {
id
species
color
}
}
}
}
}
}
}
}
}
This query is similar to something we are using in my company. What I would like to do is, for an instance of Tree
called tree
, be able to call tree.fruits
and receive an array of fruit. Right now, the workaround in kotlin would be
// As an extension file in TreeExtensions.kt
val TreeEdge.fruits: MutableList<FruitEdge> get() = {
val fruits = mutableListOf<FruitEdge>()
branches()?.branchEdges()?.forEach { branchEdge ->
branchEdge.stems()?.stemEdges()?.forEach { stemEdge ->
stemEdge.fruits()?.fruitEdges()?.forEach { fruitEdge ->
fruits.add(fruitEdge)
}
}
}
return fruits
}
On top of being very difficult to understand from a glance, it carries a potential time complexity of O(n^4), assuming we are using we are iterating over an array of TreeEdge
. I also omitted our use of fragments, which made things much more tedious to write out.
Describe the solution you'd like I think there should be a way for us to flatten the query hierarchy from GraphQL, either in the query the client writes or from the backend implementing GraphQL. What I really would love to achieve is something like this:
($treeInput: TreeInput) {
listTrees(input: $treeInput) {
fruit
}
}
so then I could just call something like treeEdge.fruits
without having to write a messy extension function that might not be performant. I understand my issue might just be badly written query code, but in my real application, the trees, branches, stems, and fruit are all important properties.
Thanks a lot for taking the time to write this 🤗 !
A few very early thoughts:
- That definitely doesn't solve the larger issue but you could
flatMap
and that'd make the extension slightly more compact:
val TreeEdge.fruits = branches()
?.branchEdges()
?.flatMap { it.stems()?.stemEdges() }
?.flatMap { it.fruits()?.fruitEdges() }
- time complexity: I don't think we can do much about that. If your backend sends back multiple trees with multiple branches, you'll have to iterate them all to find the fruit you want. The flat list will be of size mnp*q so it'll take more or less the same time to iterate than a tree.
- I think we could (should?) do a special case to shortcut the
edges
node. That'd divide by two the nesting there. - In the more general term, we might be able to do something with client directives. Something like
@passtrough(field: "trees")
. That doesn't sound easy though.
Quick thought: we might want to add something like graphql lodash to Apollo Kotlin. I think this would help for this issue.