如何使用 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-jsMongoDBrelay

/**
 * 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 查询

如何使用 apollo 客户端在 graphql 的角度处理错误?

如何使用 Apollo graphql 动态调整查询形状(字段)?