Я занимаюсь рефакторингом веб-приложения, которое интенсивно использует фасетные запросы из серверной части 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));
});
И фрагмент вывода
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
}
]
},
...