我正在尝试使用 graphql 将多个休息端点联系在一起,但我一直坚持如何过滤、排序和分页结果数据。具体来说,我需要按嵌套值过滤和/或排序。
我不能在所有情况下都对其余端点进行过滤,因为它们是具有单独数据库的单独微服务。(即我可以title
在其余端点中过滤文章,但不能在 author.name 上过滤)。排序也是如此。如果没有过滤和排序,也无法在其余端点上进行分页。
为了说明这个问题,并作为解决方案的尝试,我formatResponse
在apollo-server中使用了以下方法,但我想知道是否有更好的方法。
我已经将解决方案归结为我能想到的最小的文件集:
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}`)
);
为了便于参考,这些文件可在此 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 函数进行后处理来进行过滤/分页/排序。.
所以,我的问题是:
- 这是一个有效的用例吗?
- 有没有更规范的方法来对深度嵌套的属性进行过滤,以及排序和分页?