web-dev-qa-db-ja.com

テーブル結合のあるGraphQLクエリ

私はGraphQLを学んでいるので、小さなプロジェクトを構築しました。 UserCommentの2つのモデルがあるとします。

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つのクエリを実行します。1つはユーザー用で、もう1つはユーザーのコメント用です。
私の質問は、サーバーが1つのリクエストでクエリを実行できるように、GraphQLスキーマとタイプを構築し、テーブル間の結合を結合するためのベストプラクティスは何ですか?

24
itaied

参照している概念は、バッチ処理と呼ばれます。これを提供するいくつかのライブラリがあります。例えば:

  • Dataloader :「さまざまなバックエンド上で一貫したAPIを提供し、バッチ処理とキャッシュを介してこれらのバックエンドへのリクエストを減らす」Facebookが管理する汎用ユーティリティ

  • join-monster : "バッチデータフェッチ用のGraphQL-to-SQLクエリ実行レイヤー。"

12
marktani

.NETおよび GraphQL for .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
        }
      }
    }
  }
}

フィールドの「解決」メソッドで変数を作成できます。

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));
}
5
ceferrari