使用基于 GraphQL 游标的分页避免代码重复
Posted
技术标签:
【中文标题】使用基于 GraphQL 游标的分页避免代码重复【英文标题】:Avoid Code Duplication with GraphQL Cursor based Pagination 【发布时间】:2021-12-27 02:35:57 【问题描述】:我一直在寻找这个问题的答案,我一直在用头撞墙。我写了一个基于光标的分页示例,它适用于 graphql,问题是我认为我会对作者做同样的事情,就像我对书籍所做的那样,我能弄清楚如何做到这一点的唯一方法是完全复制所有内容。在根查询中,有相当长的代码块处理分页,我不想为作者端点做这一切,但我似乎无法在重用代码时找到这样做的方法
这里是代码
const express = require('express')
const graphqlHTTP = require('express-graphql')
const
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLList,
GraphQLInt,
GraphQLNonNull
= require('graphql')
const
PageType,
convertNodeToCursor,
convertCursorToNodeId
= require('./pagination')
const app = express()
const authors = [
id: 1, name: "Author 1",
id: 2, name: "Author 2",
id: 3, name: "Author 3"
]
const books = [
id: 1, title: "Book 1", authorId: 1 ,
id: 2, title: "Book 2", authorId: 1 ,
id: 3, title: "Book 3", authorId: 1 ,
id: 4, title: "Book 4", authorId: 2 ,
id: 5, title: "Book 5", authorId: 2 ,
id: 6, title: "Book 6", authorId: 2 ,
id: 7, title: "Book 7", authorId: 3 ,
id: 8, title: "Book 8", authorId: 3 ,
id: 9, title: "Book 9", authorId: 3
]
const Book = new GraphQLObjectType(
name: 'Book',
description: 'this is a book',
fields: () => (
id: type: GraphQLNonNull(GraphQLInt) ,
title: type: GraphQLNonNull(GraphQLString) ,
authorId: type: GraphQLNonNull(GraphQLInt) ,
author:
type: Author,
resolve: (authorId) =>
return authors.find(author => author.id === authorId)
)
)
const Author = new GraphQLObjectType(
name: 'Author',
description: 'this represents the author of a book',
fields: () => (
id: type: GraphQLNonNull(GraphQLInt) ,
name: type: GraphQLNonNull(GraphQLString) ,
books:
type: GraphQLList(Book),
resolve: (id) =>
return books.filter(book => book.authorId === id)
)
)
const RootQuery = new GraphQLObjectType(
name: 'RootQueryType',
description: 'this is the root query',
fields: () => (
book:
type: Book,
description: 'a single book',
args:
id: type: GraphQLInt
,
resolve: (_, id ) =>
return books.find(book => book.id === id)
,
author:
type: Author,
description: 'a single author',
args:
id: type: GraphQLInt ,
,
resolve: (_, id ) =>
return authors.find(author => author.id === id)
,
books:
type: PageType(Book),
description: 'a list of books',
args:
first: type: GraphQLInt ,
afterCursor: type: GraphQLString
,
resolve: (_, first, afterCursor ) =>
let afterIndex = 0
if (typeof afterCursor === 'string')
let nodeId = convertCursorToNodeId(afterCursor)
let nodeIndex = books.findIndex(book => book.id === nodeId)
if (nodeIndex >= 0)
afterIndex = nodeIndex + 1
const slicedData = books.slice(afterIndex, afterIndex + first)
console.log('sliced data: ', slicedData)
const edges = slicedData.map(node => (
node,
cursor: convertNodeToCursor(node)
))
let startCursor = null
let endCursor = null
if (edges.length > 0)
startCursor = convertNodeToCursor(edges[0].node)
endCursor = convertNodeToCursor(edges[edges.length - 1].node)
let hasNextPage = books.length > afterIndex + first
return
totalCount: books.length,
edges,
pageInfo:
startCursor,
endCursor,
hasNextPage
)
)
const schema = new GraphQLSchema(
query: RootQuery
)
app.use('/graphql', graphqlHTTP(
schema,
graphiql: true
))
app.listen(3000, () => console.log('app running at http://localhost:3000/graphql'))
我在这里处理另一个文件中的分页:
const
GraphQLString,
GraphQLInt,
GraphQLBoolean,
GraphQLObjectType,
GraphQLList,
= require('graphql')
const Edge = (itemType) =>
return new GraphQLObjectType(
name: 'EdgeType',
fields: () => (
node: type: itemType ,
cursor: type: GraphQLString
)
)
const PageInfo = new GraphQLObjectType(
name: 'PageInfoType',
fields: () => (
startCursor: type: GraphQLString ,
endCursor: type: GraphQLString ,
hasNextPage: type: GraphQLBoolean
)
)
const PageType = (itemType) =>
return new GraphQLObjectType(
name: 'PageType',
fields: () => (
totalCount: type: GraphQLInt ,
edges: type: new GraphQLList(Edge(itemType)) ,
pageInfo: type: PageInfo
)
)
const convertNodeToCursor = (node) =>
// Encoding the cursor value to Base 64 as suggested in GraphQL documentation
return Buffer.from((node.id).toString()).toString('base64')
const convertCursorToNodeId = (cursor) =>
// Decoding the cursor value from Base 64 to integer
return parseInt(Buffer.from(cursor, 'base64').toString('ascii'))
module.exports =
PageType,
convertNodeToCursor,
convertCursorToNodeId
现在,如果我复制并粘贴书籍端点并将其更改为作者,并将类型更改为 PageType(Author),那么我会收到另一个错误:
Schema must contain uniquely named types but contains multiple types named "PageType".
所以这显然也不是解决方案
【问题讨论】:
【参考方案1】:您不能拥有一个包含Author
s 的EdgeType
和另一个包含Books
的EdgeType
。相反,您需要一个 AuthorEdge
和一个 BookEdge
类型。
PageType
也是如此 - 不能有两种不同类型的不同字段但名称相同。
虽然解决方案相对简单 - 如果您在函数中动态生成这些类型,也可以动态命名它们:
const Edge = (itemType) =>
return new GraphQLObjectType(
name: itemType.name + 'Edge',
// ^^^^^^^^^^^^^^^^^^^^^^
fields: () => (
node: type: itemType ,
cursor: type: GraphQLString
)
)
const PageInfo = new GraphQLObjectType(
name: 'PageInfo',
fields: () => (
startCursor: type: GraphQLString ,
endCursor: type: GraphQLString ,
hasNextPage: type: GraphQLBoolean
)
)
const PageType = (itemType) =>
return new GraphQLObjectType(
name: itemType.name + 'sPage',
// ^^^^^^^^^^^^^^^^^^^^^^^
fields: () => (
totalCount: type: GraphQLInt ,
edges: type: new GraphQLList(Edge(itemType)) ,
pageInfo: type: PageInfo
)
)
【讨论】:
太棒了,谢谢!另一件事是我理解这是 Apollo 旨在帮助复制的方式,我想知道是否有办法避免在所有游标逻辑所在的 RootQuery 上复制书籍查询。复制整个块并在作者端点上使用它是一种耻辱 如果resolve
方法非常相似,你的意思是重复代码?
确实,RootQuery 上会有一个作者解析器,看起来与书籍解析器相同。我在想以某种方式将所有这些都提取到自己的功能中可能会更好。
嗯,不完全相同,因为它使用不同的服务/对象集合/数据库表/示例数组:-) 但是可以肯定的是,您可以编写一个辅助函数,传递该工件并返回解析器函数.以上是关于使用基于 GraphQL 游标的分页避免代码重复的主要内容,如果未能解决你的问题,请参考以下文章