如何使用 apollo / graphql 实现节点查询解析器
Posted
技术标签:
【中文标题】如何使用 apollo / graphql 实现节点查询解析器【英文标题】:How to implement a node query resolver with apollo / graphql 【发布时间】:2019-04-06 15:42:41 【问题描述】:我正在为 graphql 实现一个node interface——一种非常标准的设计模式。
寻找有关为 graphql 实现节点查询解析器的最佳方法的指导
node(id ID!): Node
我正在努力解决的主要问题是如何对 ID 和类型名进行编码/解码,以便我们可以找到正确的表/集合进行查询。
目前我正在使用带有 pgcrytpo 的 postgreSQL uuid 策略来生成 id。
应用程序中正确的接缝在哪里?:
-
可以在数据库的主键生成中完成
可以在 graphql 接缝处完成 (using a visitor pattern maybe)
一旦选择了最好的接缝:
-
如何/在哪里编码/解码?
注意我的堆栈是:
ApolloClient/Server(来自 graphql-yoga) 节点 类型ORM PostgreSQL【问题讨论】:
【参考方案1】:暴露给客户端的id
(全局对象ID)不会保留在后端——编码和解码应该由GraphQL服务器本身完成。这是一个基于中继如何实现的粗略示例:
import Foo from '../../models/Foo'
function encode (id, __typename)
return Buffer.from(`$id:$__typename`, 'utf8').toString('base64');
function decode (objectId)
const decoded = Buffer.from(objectId, 'base64').toString('utf8')
const parts = decoded.split(':')
return
id: parts[0],
__typename: parts[1],
const typeDefs = `
type Query
node(id: ID!): Node
type Foo implements Node
id: ID!
foo: String
interface Node
id: ID!
`;
// Just in case model name and typename do not always match
const modelsByTypename =
Foo,
const resolvers =
Query:
node: async (root, args, context) =>
const __typename, id = decode(args.id)
const Model = modelsByTypename[__typename]
const node = await Model.getById(id)
return
...node,
__typename,
;
,
,
Foo:
id: (obj) => encode(obj.id, 'Foo')
;
注意:通过返回 __typename
,我们让 GraphQL 的默认 resolveType
行为确定接口返回的类型,因此无需为 __resolveType
提供解析器。
编辑:将id
逻辑应用于多种类型:
function addIDResolvers (resolvers, types)
for (const type of types)
if (!resolvers[type])
resolvers[type] =
resolvers[type].id = encode(obj.id, type)
addIDResolvers(resolvers, ['Foo', 'Bar', 'Qux'])
【讨论】:
太棒了——这看起来很棒。几个问题:(1)interface Node id: String
——应该是:Node id: ID!
?和(2):id: (obj) => encode(obj.id, 'Foo')
有没有一个好地方可以把它放在所有对象上通用地处理它?
是的,id
应该是不可为空的......这就是为什么我说这是一个粗略的例子:P AFAIK,你不能为接口字段添加解析器......你必须添加扩展Node
的每种类型的解析器。请参阅我的编辑,以获取 DRY 功能的示例。【参考方案2】:
@Jonathan 我可以分享一个我拥有的实现,你看看你的想法。这是在客户端上使用graphql-js
、MongoDB
和relay
。
/**
* Given a function to map from an ID to an underlying object, and a function
* to map from an underlying object to the concrete GraphQLObjectType it
* corresponds to, constructs a `Node` interface that objects can implement,
* and a field config for a `node` root field.
*
* If the typeResolver is omitted, object resolution on the interface will be
* handled with the `isTypeOf` method on object types, as with any GraphQL
* interface without a provided `resolveType` method.
*/
export function nodeDefinitions<TContext>(
idFetcher: (id: string, context: TContext, info: GraphQLResolveInfo) => any,
typeResolver?: ?GraphQLTypeResolver<*, TContext>,
): GraphQLNodeDefinitions<TContext>
const nodeInterface = new GraphQLInterfaceType(
name: 'Node',
description: 'An object with an ID',
fields: () => (
id:
type: new GraphQLNonNull(GraphQLID),
description: 'The id of the object.',
,
),
resolveType: typeResolver,
);
const nodeField =
name: 'node',
description: 'Fetches an object given its ID',
type: nodeInterface,
args:
id:
type: GraphQLID,
description: 'The ID of an object',
,
,
resolve: (obj, id , context, info) => (id ? idFetcher(id, context, info) : null),
;
const nodesField =
name: 'nodes',
description: 'Fetches objects given their IDs',
type: new GraphQLNonNull(new GraphQLList(nodeInterface)),
args:
ids:
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLID))),
description: 'The IDs of objects',
,
,
resolve: (obj, ids , context, info) => Promise.all(ids.map(id => Promise.resolve(idFetcher(id, context, info)))),
;
return nodeInterface, nodeField, nodesField ;
然后:
import nodeDefinitions from './node';
const nodeField, nodesField, nodeInterface = nodeDefinitions(
// A method that maps from a global id to an object
async (globalId, context) =>
const id, type = fromGlobalId(globalId);
if (type === 'User')
return UserLoader.load(context, id);
....
...
...
// it should not get here
return null;
,
// A method that maps from an object to a type
obj =>
if (obj instanceof User)
return UserType;
....
....
// it should not get here
return null;
,
);
load
方法解析实际对象。这部分您将更具体地使用您的数据库等...
不清楚的可以问!希望对你有帮助:)
【讨论】:
以上是关于如何使用 apollo / graphql 实现节点查询解析器的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 apollo ios 客户端实现 graphql 订阅
如何使“apollo 服务器”与 graphql schemaObject 一起使用?
如何使用 Apollo graphql 实现 react-sortable-hoc?
如何使用 apollo 服务器从变量运行 graphql 查询