【发布时间】:2017-03-06 11:16:40
【问题描述】:
我正在尝试使用 graphql 将多个休息端点联系在一起,但我一直在思考如何过滤、排序和分页生成的数据。具体来说,我需要按嵌套值过滤和/或排序。
我无法在所有情况下都对其余端点进行过滤,因为它们是具有独立数据库的独立微服务。 (即,我可以在文章的其余端点中过滤 title,但不能在 author.name 上过滤)。排序也是如此。而且没有过滤和排序,其余端点也无法进行分页。
为了说明问题并尝试解决问题,我在apollo-server 中使用formatResponse 提出了以下建议,但我想知道是否有更好的方法。
我已将解决方案归结为我能想到的最小文件集:
data.js 表示 2 个虚构的休息端点将返回的内容:
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') && variables.offset > 0) {
data = _.slice(data, variables.offset);
}
if ({}.hasOwnProperty.call(variables, 'limit') && 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}`)
);
为便于参考,这些文件可在this gist 获得。
通过这个设置,我可以发送这个查询:
{
articles {
id
title
author {
id
name
}
}
}
连同这些变量(看起来这不是变量的预期用途,但这是我可以将后处理参数放入 formatResponse 函数的唯一方法。):
{ "q": "author/name=Sam", "sort": "-id", "offset": 1, "limit": 1 }
并获取此响应,过滤到 Sam 是作者的位置,按 id 降序排序,并获取页面大小为 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 函数进行后处理来进行过滤/分页/排序。 .
所以,我的问题是:
- 这是一个有效的用例吗?
- 除了排序和分页之外,还有更规范的方法来对深度嵌套的属性进行过滤吗?
【问题讨论】:
-
我知道您没有使用中继,但您是否阅读过有关中继connections 的信息?我相信它将帮助您了解如何请求分页集合。现在,关于如何在您的架构中过滤和分页(我自己手头有一个类似的),我相信您唯一的解决方案是在某个地方与您的数据相交。以您的示例为例,如果您想按
author.name过滤,您必须首先搜索具有该名称的作者,然后然后搜索具有这些作者的文章。 -
还没有使用
GraphQL,但是在考虑分页的情况下对该主题进行了一些研究,我偶然发现了这篇文章Understanding Pagination REST GraphQL and Relay,其中谈到了即将推出的分页功能。这可能有助于回答您的相关问题。 -
以 author.name 开头的问题在于,您假设作者类型将从可以有效排序的单一来源解析。但是在分片环境中,我们可能有两个或多个底层数据源需要两个独立的查询,最终都得到作者结果。据我所知,进行此处讨论的那种复杂排序的唯一通用方法是使用过滤器过程,该过程明确设计用于对 graphql 结果进行排序。
标签: microservices graphql graphql-js apollo-server