【问题标题】:Is GraphQL suitable for retrieving multi-level facet data from a back-end without making multiple queries?GraphQL 是否适合在不进行多次查询的情况下从后端检索多级 facet 数据?
【发布时间】:2020-06-05 03:24:22
【问题描述】:

我正在重构一个 Web 应用程序,该应用程序大量使用来自 Solr 后端的方面查询。特别是,这些是多层次的嵌套构面:例如,先对人的眼睛颜色进行构面,然后再对头发的颜色进行构面(如下面的代码示例所示)。构面结果应该是计数而不是实际记录及其字段。

我对使用 GraphQL 来解耦前端和后端很感兴趣,但我认为我无法绕过基于字段的解析执行模型。那么在 Solr 中的单个查询是什么,按眼睛颜色返回人数,然后(在每个眼睛颜色方面)按头发颜色返回,似乎必须通过 GraphQL 中的多个查询来完成。

我制作了一个独立的 nodejs 示例,其中只有两个依赖项(graphql 和 graphql-tools),它展示了我想要实现的目标。有一个facetize(data, fields) 函数可以模拟 Solr 可以做什么。它对给定字段的数据进行计数。如果给定了多个字段,那么它会按照上述方式进行嵌套分面。

我将此函数插入​​解析器,以便在每个所需级别仅使用一个字段调用它。它工作正常,但当然要多次调用facetize()

我的问题是,能否以某种方式设置解析器,以便只对facetize 进行一次查询,但仍返回相同的嵌套分面数据?我不介意查询是否被展平(例如facets(field1: "eyeColor", field2: "hairColor") { ... }),但我玩了一下,结果只包含第一级计数。

而且...对于 Solr 人员来说还有一个额外的问题,对子方面进行多个 Solr 查询是否同样有效?或者甚至更高效,如果它可以被 GraphQL 执行引擎并行化?是否值得至少模拟一些东西来进行基准测试?

var { graphql, buildSchema } = require('graphql');
var { makeExecutableSchema } = require('graphql-tools');


// GraphQL schema
const typeDefs = `

type Query {
  facets(field: String!) : [Facet!]!
}

type Facet {
  value : String!
  field : String!
  count : Int!
  facets(field: String!) : [Facet!]!
}
`;


// demo data
const data = [
  { name: 'John',
    eyeColor: 'blue',
    hairColor: 'brown'
  },
  { name: 'Jane',
    eyeColor: 'blue',
    hairColor: 'blonde'
  },
  { name: 'Jay',
    eyeColor: 'green',
    hairColor: 'black'
  },
  { name: 'Julie',
    eyeColor: 'blue',
    hairColor: 'brown'
  },
  { name: 'Jamal',
    eyeColor: 'brown',
    hairColor: 'black'
  },
  { name: 'Jack',
    eyeColor: 'green',
    hairColor: 'blonde'
  },
  { name: 'Jill',
    eyeColor: 'blue',
    hairColor: 'brown'
  }
];


// this facetize() function is a simple recreation of
// the functionality that Solr can perform in just one request
// (keeping all the data Solr-server-side)
//
//   var facets = facetize(data, ['eyeColor', 'hairColor']);
//   console.log(JSON.stringify(facets, null, 2));
//   // though it would display cleaner with the data fields stripped out

function facetize(data, flds, message) {
  // log first-time entry to this recursive function
  if (message) console.log("entering facetize from "+message+" using fields "+flds);

  let fields = flds.slice();  // make a deep-ish copy
  let field = fields.shift(); // deal with the first field first

  // set up the empty facet objects for each unique value of field 'field'
  let uniqueValues = [...new Set(data.map(elem => elem[field]))];
  let facets = {};
  uniqueValues.forEach(value =>
               facets[value] =
               { value: value, field: field, count: 0, data: [] });

  // now iterate through data counting occurrences and
  // storing the data items in facet.data
  data.forEach( item => {
    let value = item[field];
    facets[value].count++;
    facets[value].data.push(item);
  });

  // if there are fields left to facet on, do so recursively
  if (fields.length) {
    uniqueValues.forEach(value =>
             facets[value].facets =
             facetize(facets[value].data, fields));
  }
  return uniqueValues.map( value => facets[value] );
}


// The root provides a resolver function for each API endpoint
const resolvers = {
  Query : {
    facets: (_, { field }) => facetize(data, [field], "Query.facets")
  },
  Facet : {
    facets: (obj, { field }) => facetize(obj.data, [field], "Facet.facets")
  }
};


// do some schema magic
const schema = makeExecutableSchema({ typeDefs, resolvers })


// Run the GraphQL query and print out the response
graphql(
  schema, `
 {
   facets(field: "eyeColor") { 
     value
     field
     count
     facets(field: "hairColor") {
       value
       field
       count
     }
   }
 }`
).then((response) => {
  console.log(JSON.stringify(response,null,2));
});

还有一个输出的sn-p

entering facetize from Query.facets using fields eyeColor
entering facetize from Facet.facets using fields hairColor
entering facetize from Facet.facets using fields hairColor
entering facetize from Facet.facets using fields hairColor
{
  "data": {
    "facets": [
      {
        "value": "blue",
        "field": "eyeColor",
        "count": 4,
        "facets": [
          {
            "value": "brown",
            "field": "hairColor",
            "count": 3
          },
          {
            "value": "blonde",
            "field": "hairColor",
            "count": 1
          }
        ]
      },
      {
        "value": "green",
        "field": "eyeColor",
        "count": 2,
        "facets": [
          {
            "value": "black",
            "field": "hairColor",
            "count": 1
          },
          {
            "value": "blonde",
            "field": "hairColor",
            "count": 1
          }
        ]
      },
...

【问题讨论】:

    标签: node.js solr graphql


    【解决方案1】:

    您遇到的是 N+1 问题。您应该可以使用dataloader 解决此问题。 Ben 有一个很棒的教程 here 你可以看看。他还包括解决问题的其他方法

    【讨论】:

    • 答案本身应包含任何相关信息。不要要求用户通过挖掘(或在这种情况下观看视频)来查找相关信息。该信息也可能随时消失或内容发生变化。
    • 感谢@MatsLindh 的反馈
    • 这是一条我 (OP) 很乐意旅行的大道。 Dataloader 似乎对任何现实世界的实现都非常重要。谢谢罗马里奥。
    猜你喜欢
    • 1970-01-01
    • 2019-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-28
    • 2021-11-18
    • 1970-01-01
    相关资源
    最近更新 更多