GraphQL 是不是适合在不进行多次查询的情况下从后端检索多级 facet 数据?

Posted

技术标签:

【中文标题】GraphQL 是不是适合在不进行多次查询的情况下从后端检索多级 facet 数据?【英文标题】: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
          
        ]
      ,
...

【问题讨论】:

【参考方案1】:

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

【讨论】:

答案本身应包含任何相关信息。不要要求用户通过挖掘(或在这种情况下观看视频)来查找相关信息。该信息也可能随时消失或内容发生变化。 感谢@MatsLindh 的反馈 这是一条我 (OP) 很乐意旅行的大道。 Dataloader 似乎对任何现实世界的实现都非常重要。谢谢罗马里奥。

以上是关于GraphQL 是不是适合在不进行多次查询的情况下从后端检索多级 facet 数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不编写长查询的情况下查询所有 GraphQL 类型字段?

如何在不编写长查询的情况下查询所有 GraphQL 类型字段?

我可以在不使用 Apollo 的情况下使用 graphql 进行反应吗?

Gatsby + Contentful - 如何在不重新启动服务器的情况下在本地重做 GraphQL 查询(npm run dev)?

如何让 GraphQL 在不进行轮询的情况下从数据库中获取实时/新数据?

我可以在不使用 prisma 的情况下在我的 graphQL 服务器中使用 MongoDB 吗?