sql - postgres - sequelize mongodb
Consultas GraphQL con tablas unidas (2)
Estoy aprendiendo GraphQL
así que construí un pequeño proyecto. Digamos que tengo 2 modelos, User
y 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,
});
Las relaciones son 1: muchas, donde un usuario puede tener muchos comentarios.
He construido el esquema de esta manera:
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()
}
})
});
Y la consulta:
const user = {
type: UserType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve(_, {id}) => User.findById(id)
};
La ejecución de la consulta para un usuario y sus comentarios se realiza con 1 solicitud, así:
{
User(id:"1"){
Comments{
content
}
}
}
Según tengo entendido, el cliente obtendrá los resultados utilizando 1 consulta, este es el beneficio de utilizar GraphQL
. Pero el servidor ejecutará 2 consultas, una para el usuario y otra para sus comentarios.
Mi pregunta es, ¿cuáles son las mejores prácticas para crear el esquema y los tipos de GraphQL
y combinar la combinación entre tablas, de modo que el servidor también pueda ejecutar la consulta con 1 solicitud?
El concepto al que se refiere se llama procesamiento por lotes. Hay varias bibliotecas por ahí que ofrecen esto. Por ejemplo:
Dataloader : utilidad genérica mantenida por Facebook que proporciona "una API consistente en varios backends y reduce las solicitudes a esos backends a través de lotes y almacenamiento en caché"
join-monster : "Una capa de ejecución de consultas GraphQL a SQL para la obtención de datos por lotes".
Para cualquier persona que use .NET y el paquete GraphQL for .NET , he creado un método de extensión que convierte la consulta de GraphQL en Entity Framework Incluye.
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;
}
}}}
Digamos que tenemos la siguiente consulta:
query {
getHistory {
id
product {
id
category {
id
subCategory {
id
}
subAnything {
id
}
}
}
}
}
Podemos crear una variable en el método "resolver" del campo:
var include = context.GetIncludeString();
que genera la siguiente cadena:
"Product.Category.SubCategory,Product.Category.SubAnything"
y pasarlo a Entity Framework:
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));
}