web-dev-qa-db-fra.com

Comment modéliser des structures de données récursives dans GraphQL

J'ai une structure de données arborescente que je voudrais retourner via une API GraphQL.

La structure n'est pas particulièrement grande (suffisamment petite pour ne pas être un problème pour la renvoyer en un seul appel).

La profondeur maximale de la structure n'est pas définie.

J'ai modélisé la structure comme quelque chose comme:

type Tag{
    id: String!
    children: [Tag]
}

Le problème apparaît lorsque l'on veut obtenir les balises à une profondeur arbitraire.

Pour amener tous les enfants au niveau 3 (par exemple), on écrirait une requête comme:

{ tags { id children { id children { id } } } }

Existe-t-il un moyen d'écrire une requête pour renvoyer toutes les balises à une profondeur arbitraire?

Sinon, quelle est la méthode recommandée pour modéliser une structure comme celle ci-dessus dans une API GraphQL.

17
raduw

Il y a quelque temps, j'ai trouvé une autre solution, qui est la même approche que celle suggérée par @WuDo.

L'idée est d'aplatir l'arborescence au niveau des données en utilisant des ID pour les référencer (chaque enfant avec son parent) et en marquant les racines de l'arborescence, puis du côté client, reconstituer l'arborescence de manière récursive.

De cette façon, vous ne devriez pas vous soucier de limiter la profondeur de votre requête comme dans la réponse de @ samcorcos.

schéma:

type Query {
    tags: [Tag]
}

type Tag {
    id: ID!
    children: [ID]
    root: Boolean
}

réponse:

{ 
    "tags": [
        {"id": "1", "children": ["2"], "root": true}, 
        {"id": "2", "children": [], "root": false}
    ] 
}

accumulation d'arborescence client:

import find from 'lodash/find';
import isArray from 'lodash/isArray';

const rootTags = [...tags.map(obj => {...obj)}.filter(tag => tag.root === true)];
const mapChildren = childId => {
    const tag = find(tags, tag => tag.id === childId) || null;

    if (isArray(tag.children) && tag.children.length > 0) {
        tag.children = tag.children.map(mapChildren).filter(tag => tag !== null);
    }
}
const tagTree = rootTags.map(tag => {
    tag.children = tag.children.map(mapChildren).filter(tag => tag !== null);
    return tag;
});
11
tlenex

Votre meilleur pari est de passer un paramètre et d'utiliser ce paramètre dans votre résolveur. Votre syntaxe variera en fonction du modèle que vous avez adopté, mais c'est l'essentiel.

/* 
  Add an argument to your query:
  
  query {
    tags(depth: 3) { 
      id 
      children
    }
  }
*/

export default {
  Query: {
    tags: async (obj, { depth, }, context) => {
      // depending on how you're getting tags, run the function
      // that gets you a list of tags
      const tags = await getTags(obj, { depth, }, context)
        // depending on which ORM you're using, join 
        // `depth` number of times here on `tags.children`
      
      return tags
    }
  }
}

Évidemment, chaque fois que vous interrogez de manière récursive comme celle-ci, vous risquez une explosion de base de données, mais tant que vous savez ce que vous faites, ça devrait aller.

0
samcorcos