【问题标题】:How to implement a node query resolver with apollo / graphql如何使用 apollo / graphql 实现节点查询解析器
【发布时间】:2019-04-06 15:42:41
【问题描述】:

我正在为 graphql 实现一个node interface——一种非常标准的设计模式。

寻找有关为 graphql 实现节点查询解析器的最佳方法的指导

node(id ID!): Node

我正在努力解决的主要问题是如何对 ID 和类型名进行编码/解码,以便我们可以找到正确的表/集合进行查询。

目前我正在使用带有 pgcrytpo 的 postgreSQL uuid 策略来生成 id。

应用程序中正确的接缝在哪里?:

  1. 可以在数据库的主键生成中完成
  2. 可以在 graphql 接缝处完成 (using a visitor pattern maybe)

一旦选择了最好的接缝:

  1. 如何/在哪里编码/解码?

注意我的堆栈是:

  • ApolloClient/Server(来自 graphql-yoga)
  • 节点
  • 类型ORM
  • PostgreSQL

【问题讨论】:

    标签: postgresql graphql apollo apollo-server typeorm


    【解决方案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 方法解析实际对象。这部分您将更具体地使用您的数据库等... 不清楚的可以问!希望对你有帮助:)

    【讨论】:

      猜你喜欢
      • 2023-03-13
      • 2021-11-30
      • 2020-09-12
      • 2018-06-24
      • 2022-07-20
      • 2019-06-13
      • 2020-03-21
      • 1970-01-01
      • 2021-07-25
      相关资源
      最近更新 更多