Best practices for modeling recursive parent relationships in GraphQL #1194
-
|
Hi everyone! I'm modeling a hierarchical structure (promotion groups) in GraphQL. The current schema looks like this: type PromotionGroupLinked {
id: String!
name: String!
parent: PromotionGroupLinked
promotionCount: Int!
}So the My main concern isn't malicious queries but efficiency:
So, it's fine to keep the How you usually model these kinds of hierarchies in GraphQL and what trade-offs you consider when deciding between a recursive design and a simplified "flat" parent type? |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
|
I think there were some explorations of a transport that would normalize the response so that repeated entities are sent only once but I can't find a reference sadly. My 2 cents would be that, especially with a depth of 1, the default of sending the same repeated entity multiple times is probably good enough (gzip can probably help). If you want to squeeze the maximum perfomance of your API, this is probably a good use case for a specialized binary protocol (see also #432). I'd love to see this happening some day. |
Beta Was this translation helpful? Give feedback.
-
|
Hi @molostovvs; how I solve this in my own projects is to flatten trees into lists and then reconstitute them as trees client side: {
promotionGroupsFlattened(depth: 3) {
id
name
promotionCount
parent { id }
}
}This eliminates redundancy since each node is only returned once, and also allows for arbitrarily deep tree reconstruction without having to write recursive (or exhaustive) GraphQL queries, at the cost of a little effort on the client side; something like: type Tree<T> = T & {
children: ReadonlyArray<Tree<T>>
}
function listToTree<T extends { id: string, parent: { id: string } | null }>(
nodes: ReadonlyArray<T>
): Tree<T> {
const clonedNodeById = nodes.reduce(
(memo, n) => {memo[n.id] = {...n, children: []};return memo},
Object.create(null) as Object<string, Tree<T>>
);
let rootNode = null;
for (const node of Object.values(clonedNodeById)) {
const parentId = node.parent?.id;
if (parentId != null) {
const parent = clonedNodeById[parentId];
if (!parent) throw new Error(`Could not find parent node with id ${parentId}`);
parent.children.push(node);
} else {
if (rootNode != null) throw new Error("Multiple root nodes?!")
rootNode = node;
}
}
return rootNode;
} |
Beta Was this translation helpful? Give feedback.
I think there were some explorations of a transport that would normalize the response so that repeated entities are sent only once but I can't find a reference sadly.
My 2 cents would be that, especially with a depth of 1, the default of sending the same repeated entity multiple times is probably good enough (gzip can probably help).
If you want to squeeze the maximum perfomance of your API, this is probably a good use case for a specialized binary protocol (see also #432). I'd love to see this happening some day.