Подтвердить что ты не робот

Запросы GraphQL с таблицами объединяются

Я изучаю GraphQL, поэтому я создал небольшой проект. Скажем, у меня есть 2 модели, User и 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,

});

Отношения 1: многие, где пользователь может иметь много комментариев.
Я построил схему следующим образом:

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()
    }
  })
});

И запрос:

const user = {
  type: UserType,
  args: {
    id: {
      type: new GraphQLNonNull(GraphQLString)
    }
  },
  resolve(_, {id}) => User.findById(id)
};

Выполнение запроса для пользователя и его комментариев выполняется с помощью 1 запроса, например:

{
  User(id:"1"){
    Comments{
      content
    }
  }
}

Как я понимаю, клиент получит результаты с использованием 1 запроса, это преимущество с помощью GraphQL. Но сервер выполнит 2 запроса, один для пользователя и еще один для его комментариев.
Мой вопрос в том, каковы наилучшие методы построения схемы и типов GraphQL и объединения соединений между таблицами, чтобы сервер мог также выполнить запрос с 1 запросом?

4b9b3361

Ответ 1

Концепция, на которую вы ссылаетесь, называется пакетной. Есть несколько библиотек, которые предлагают это. Например:

  • Dataloader: универсальная утилита, поддерживаемая Facebook, которая обеспечивает "совместимый API-интерфейс по различным бэкендам и уменьшает запросы к этим бэкэндам через докетирование и кеширование"

  • join-monster: "Уровень выполнения запросов GraphQL-to-SQL для извлечения пакетных данных".

Ответ 2

Для тех, кто использует .NET и пакет GraphQL для .NET, я создал метод расширения, который преобразует запрос GraphQL в 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;
            }
        }}}

Допустим, у нас есть следующий запрос:

query {
  getHistory {
    id
    product {
      id
      category {
        id
        subCategory {
          id
        }
        subAnything {
          id
        }
      }
    }
  }
}

Мы можем создать переменную в методе "resol" поля:

var include = context.GetIncludeString();

который генерирует следующую строку:

"Product.Category.SubCategory,Product.Category.SubAnything"

и передать его в 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));
}