apollo-kotlin icon indicating copy to clipboard operation
apollo-kotlin copied to clipboard

Query Field Flattening

Open davidgalindo opened this issue 4 years ago • 2 comments

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.

davidgalindo avatar Jan 26 '21 20:01 davidgalindo

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.

martinbonnin avatar Jan 26 '21 20:01 martinbonnin

Quick thought: we might want to add something like graphql lodash to Apollo Kotlin. I think this would help for this issue.

martinbonnin avatar Feb 08 '22 09:02 martinbonnin