如何使用 apollo / graphql 实现节点查询解析器
How to implement a node query resolver with apollo / graphql
我正在为 graphql 实现一个 node interface -- 一个非常标准的设计模式。
寻找有关为 graphql 实施节点查询解析器的最佳方法的指导
node(id ID!): Node
我遇到的主要问题是如何 encode/decode 类型名称的 ID,以便我们可以找到正确的 table/collection 进行查询。
目前我正在使用带有 pgcrytpo 的 postgreSQL uuid 策略来生成 id。
执行此操作的应用程序中的正确接缝在哪里?:
- 可以在数据库的主键生成中完成
- 可以在 graphql 接缝处完成 (using a visitor pattern maybe)
选择最佳接缝后:
- how/where你encode/decode吗?
注意我的堆栈是:
- ApolloClient/Server(来自 graphql-yoga)
- 节点
- 类型ORM
- PostgreSQL
@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
方法解析实际对象。这部分你会更具体地与你的数据库等一起工作......
不清楚的可以追问!希望对您有所帮助:)
暴露给客户端的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
行为确定接口返回的类型,因此无需为 [=15= 提供解析器].
编辑:将 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'])
我正在为 graphql 实现一个 node interface -- 一个非常标准的设计模式。
寻找有关为 graphql 实施节点查询解析器的最佳方法的指导
node(id ID!): Node
我遇到的主要问题是如何 encode/decode 类型名称的 ID,以便我们可以找到正确的 table/collection 进行查询。
目前我正在使用带有 pgcrytpo 的 postgreSQL uuid 策略来生成 id。
执行此操作的应用程序中的正确接缝在哪里?:
- 可以在数据库的主键生成中完成
- 可以在 graphql 接缝处完成 (using a visitor pattern maybe)
选择最佳接缝后:
- how/where你encode/decode吗?
注意我的堆栈是:
- ApolloClient/Server(来自 graphql-yoga)
- 节点
- 类型ORM
- PostgreSQL
@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
方法解析实际对象。这部分你会更具体地与你的数据库等一起工作......
不清楚的可以追问!希望对您有所帮助:)
暴露给客户端的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
行为确定接口返回的类型,因此无需为 [=15= 提供解析器].
编辑:将 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'])