Les requêtes GraphQL avec les tables de jointure
je suis en train d'apprendre GraphQL
alors j'ai construit un petit projet. Disons que j'ai 2 modèles, User
et Comment
.
const Comment = Model.define('Comment', {
content: {
type: DataType.TEXT,
allowNull: false,
validate: {
notEmpty: true,
},
},
});
const User = Model.define('User', {
name: {
type: DataType.STRING,
allowNull: false,
validate: {
notEmpty: true,
},
},
phone: DataType.STRING,
picture: DataType.STRING,
});
les relations sont 1: many, où un utilisateur peut avoir beaucoup de commentaires.
J'ai construit le schéma comme ceci:
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: {
type: GraphQLString
},
name: {
type: GraphQLString
},
phone: {
type: GraphQLString
},
comments: {
type: new GraphQLList(CommentType),
resolve: user => user.getComments()
}
})
});
Et la requête:
const user = {
type: UserType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(_, {id}) => User.findById(id)
};
exécuter la requête pour un utilisateur et ses commentaires se font avec 1 requête, comme ceci:
{
User(id:"1"){
Comments{
content
}
}
}
si je comprends bien, le client obtiendra les résultats en utilisant 1 requête, c'est l'avantage d'utiliser GraphQL
. Mais le serveur exécutera 2 requêtes, une pour l'utilisateur et une autre pour ses commentaires.
Ma question Est, quelles sont les meilleures pratiques pour construire le GraphQL
schema et types et combinaison de jointure entre les tables, de sorte que le serveur pourrait également exécuter la requête avec 1 requête?
2 réponses
le concept auquel vous faites référence est appelé batching. Il existe plusieurs bibliothèques qui offrent ce. Par exemple:
Dataloader: utilitaire Générique maintenu par Facebook qui fournit "une API cohérente sur les différents backends et réduit les requêtes à ces backends via batching et caching"
rejoignez-monster: "Un GraphQL-à-requête SQL couche d'exécution pour le lot de données récupérer."
Pour quiconque de l'utiliser .NET et le GraphQL pour .NET paquet, j'ai fait une méthode d'extension qui convertit la requête GraphQL en Entity Framework Includes.
public static class ResolveFieldContextExtensions
{
public static string GetIncludeString(this ResolveFieldContext<object> source)
{
return string.Join(',', GetIncludePaths(source.FieldAst));
}
private static IEnumerable<Field> GetChildren(IHaveSelectionSet root)
{
return root.SelectionSet.Selections.Cast<Field>().Where(x => x.SelectionSet.Selections.Any());
}
private static IEnumerable<string> GetIncludePaths(IHaveSelectionSet root)
{
var q = new Queue<Tuple<string, Field>>();
foreach (var child in GetChildren(root))
{
q.Enqueue(new Tuple<string, Field>(child.Name.ToPascalCase(), child));
}
while (q.Any())
{
var node = q.Dequeue();
var children = GetChildren(node.Item2).ToList();
if (children.Any())
{
foreach (var child in children)
{
q.Enqueue(new Tuple<string, Field>(node.Item1 + "." + child.Name.ToPascalCase(), child));
}
}
else
{
yield return node.Item1;
}
}
}
}
disons que nous avons la requête suivante:
query {
getHistory {
id
product {
id
category {
id
subCategory {
id
}
subAnything {
id
}
}
}
}
}
nous pouvons créer une variable dans la méthode" resolve " du champ:
var include = context.GetIncludeString();
qui génère la chaîne suivante:
"Product.Category.SubCategory,Product.Category.SubAnything"
et le transmettre à l'Entité Framwork:
public Task<TEntity> Get(TKey id, string include)
{
var query = Context.Set<TEntity>();
if (!string.IsNullOrEmpty(include))
{
query = include.Split(',', StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (q, p) => q.Include(p));
}
return query.SingleOrDefaultAsync(c => c.Id.Equals(id));
}