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)?