#microservices #graphql #graphql-js #apollo-server
#микросервисы #graphql #graphql-js #apollo-сервер
Вопрос:
Я пытаюсь использовать graphql для объединения нескольких конечных точек rest, и я застрял на том, как фильтровать, сортировать и разбивать на страницы результирующие данные. В частности, мне нужно отфильтровать и / или отсортировать по вложенным значениям.
Я не могу выполнить фильтрацию на конечных точках rest во всех случаях, потому что это отдельные микросервисы с отдельными базами данных. (т. Е. я мог бы выполнять фильтрацию на title
в конечной точке rest для статей, но не на author.name ). Аналогично с сортировкой. А без фильтрации и сортировки разбивка на страницы также не может быть выполнена на конечных точках rest.
Чтобы проиллюстрировать проблему и в качестве попытки решения, я придумал следующее, используя formatResponse
в apollo-server, но мне интересно, есть ли способ получше.
Я свел решение к самому минимальному набору файлов, который только мог придумать:
data.js представляет то, что было бы возвращено двумя вымышленными конечными точками rest:
export const Authors = [{ id: 1, name: 'Sam' }, { id: 2, name: 'Pat' }];
export const Articles = [
{ id: 1, title: 'Aardvarks', author: 1 },
{ id: 2, title: 'Emus', author: 2 },
{ id: 3, title: 'Tapir', author: 1 },
]
схема определяется как:
import _ from 'lodash';
import {
GraphQLSchema,
GraphQLObjectType,
GraphQLList,
GraphQLString,
GraphQLInt,
} from 'graphql';
import {
Articles,
Authors,
} from './data';
const AuthorType = new GraphQLObjectType({
name: 'Author',
fields: {
id: {
type: GraphQLInt,
},
name: {
type: GraphQLString,
}
}
});
const ArticleType = new GraphQLObjectType({
name: 'Article',
fields: {
id: {
type: GraphQLInt,
},
title: {
type: GraphQLString,
},
author: {
type: AuthorType,
resolve(article) {
return _.find(Authors, { id: article.author })
},
}
}
});
const RootType = new GraphQLObjectType({
name: 'Root',
fields: {
articles: {
type: new GraphQLList(ArticleType),
resolve() {
return Articles;
},
}
}
});
export default new GraphQLSchema({
query: RootType,
});
И основной index.js является ли:
import express from 'express';
import { apolloExpress, graphiqlExpress } from 'apollo-server';
var bodyParser = require('body-parser');
import _ from 'lodash';
import rql from 'rql/query';
import rqlJS from 'rql/js-array';
import schema from './schema';
const PORT = 8888;
var app = express();
function formatResponse(response, { variables }) {
let data = response.data.articles;
// Filter
if ({}.hasOwnProperty.call(variables, 'q')) {
// As an example, use a resource query lib like https://github.com/persvr/rql to do easy filtering
// in production this would have to be tightened up alot
data = rqlJS.query(rql.Query(variables.q), {}, data);
}
// Sort
if ({}.hasOwnProperty.call(variables, 'sort')) {
const sortKey = _.trimStart(variables.sort, '-');
data = _.sortBy(data, (element) => _.at(element, sortKey));
if (variables.sort.charAt(0) === '-') _.reverse(data);
}
// Pagination
if ({}.hasOwnProperty.call(variables, 'offset') amp;amp; variables.offset > 0) {
data = _.slice(data, variables.offset);
}
if ({}.hasOwnProperty.call(variables, 'limit') amp;amp; variables.limit > 0) {
data = _.slice(data, 0, variables.limit);
}
return _.assign({}, response, { data: { articles: data }});
}
app.use('/graphql', bodyParser.json(), apolloExpress((req) => {
return {
schema,
formatResponse,
};
}));
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
app.listen(
PORT,
() => console.log(`GraphQL Server running at http://localhost:${PORT}`)
);
For ease of reference, these files are available at this gist.
With this setup, I can send this query:
{
articles {
id
title
author {
id
name
}
}
}
Along with these variables (It seems like this is not the intended use for the variables, but it was the only way I could get the post processing parameters into the formatResponse function.):
{ "q": "author/name=Sam", "sort": "-id", "offset": 1, "limit": 1 }
и получите этот ответ, отфильтрованный до того, где Сэм является автором, отсортированный по убыванию идентификатора и получающий получение второй страницы, где размер страницы равен 1.
{
"data": {
"articles": [
{
"id": 1,
"title": "Aardvarks",
"author": {
"id": 1,
"name": "Sam"
}
}
]
}
}
Или эти переменные:
{ "sort": "-author.name", "offset": 1 }
Для этого ответа отсортируйте по имени автора по убыванию и получите все статьи, кроме первой.
{
"data": {
"articles": [
{
"id": 1,
"title": "Aardvarks",
"author": {
"id": 1,
"name": "Sam"
}
},
{
"id": 2,
"title": "Emus",
"author": {
"id": 2,
"name": "Pat"
}
}
]
}
}
Итак, как вы можете видеть, я использую функцию formatResponse для последующей обработки для выполнения фильтрации / разбиения на страницы / сортировки. .
Итак, мои вопросы:
- Является ли это допустимым вариантом использования?
- Есть ли более канонический способ выполнить фильтрацию по глубоко вложенным свойствам наряду с сортировкой и разбиением на страницы?
Комментарии:
1. Я знаю, что вы не используете relay, но читали ли вы о соединениях relay? Я полагаю, что это поможет вам понять, как запрашивать коллекции с разбивкой на страницы. Теперь, что касается того, как фильтровать и разбивать на страницы в вашей архитектуре (у меня у самого есть похожий), я считаю, что ваше единственное решение — где-то пересекать ваши данные. Исходя из вашего примера, если вы хотите выполнить фильтрацию по
author.name
, вам придется сначала выполнить поиск авторов с этим именем, а затем выполнить поиск статей с этими авторами.2.
GraphQL
Еще не использую, но провожу некоторое исследование по этому вопросу с учетом разбивки на страницы, и я наткнулся на эту статью Understanding Pagination REST GraphQL and Relay , в которой рассказывается о предстоящей функции разбивки на страницы. Это может быть полезно для ответа на ваши вопросы по этому поводу.3. Проблема с запуском с author.name вы предполагаете, что тип автора будет разрешен из одного источника, который может быть эффективно отсортирован. Но в разделенной среде у нас может быть два или более базовых источника данных, требующих двух изолированных запросов, оба из которых заканчиваются авторскими результатами. Насколько я могу судить, единственный универсальный способ выполнить описанную здесь сложную сортировку — это процесс фильтрации, который явно предназначен для сортировки результатов graphql.
Ответ №1:
Является ли это допустимым вариантом использования? Есть ли более канонический способ выполнить фильтрацию по глубоко вложенным свойствам наряду с сортировкой и разбиением на страницы?
Основная часть первоначального поиска заключается в разделении коллекций из разных баз данных на отдельных микросервисах. На самом деле, необходимо выполнить объединение коллекции и последующую фильтрацию по некоторому ключу, но это напрямую невозможно, поскольку в исходной коллекции нет поля для фильтрации, сортировки или разбиения на страницы.
Простым решением является выполнение полных или отфильтрованных запросов к исходным коллекциям, а затем объединение и фильтрация результирующего набора данных на сервере приложений, например Lodash, например, в вашем решении. Это возможно для небольших коллекций, но в общем случае приводит к большой передаче данных и неэффективной сортировке, поскольку нет структуры индекса — реального RB-дерева или списка пропусков, поэтому с квадратичной сложностью это не очень хорошо.
В зависимости от объема ресурсов на сервере приложений там могут быть созданы специальные таблицы кэша и индексов. Если структура коллекции исправлена, некоторые связи между записями коллекции и их полями могут быть отражены в специальной таблице поиска и обновлены соответственно на demain. Это похоже на создание индекса поиска, но не в базе данных, а на сервере приложений. Конечно, это будет потреблять ресурсы, но будет быстрее, чем прямая сортировка Lodash-подобным образом.
Также задача может быть решена с другой стороны, если есть доступ к структуре исходных баз данных. Ключ — это денормализация. В отличие от классического подхода к связям, коллекции могут содержать дублирующую информацию для кодирования дальнейшей операции объединения. Например, коллекция статей может содержать некоторую информацию из коллекции авторов, которая необходима для выполнения фильтрации, сортировки и разбивки на страницы в дальнейших операциях.