Как реализовать преобразователь запросов узла с помощью apollo / graphql - PullRequest
0 голосов
/ 02 ноября 2018

Я работаю над реализацией интерфейса узла для graphql - довольно стандартного шаблона проектирования.

Ищем руководство по наилучшему способу реализации распознавателя запросов узла для graphql

node(id ID!): Node

Главное, с чем я борюсь, это как закодировать / декодировать идентификатор типа, чтобы мы могли найти нужную таблицу / коллекцию для запроса.

В настоящее время я использую стратегию postgreSQL uuid с pgcrytpo для генерации идентификаторов.

Где в приложении правильный шов для этого?:

  1. может быть сделано при генерации первичного ключа в базе данных
  2. может быть сделано в шве graphql ( с использованием шаблона посетителя, может быть )

И как только лучший шов выбран:

  1. как / где вы кодируете / декодируете?

Обратите внимание, мой стек:

  • ApolloClient / Server (от graphql-yoga)
  • узел * +1036 *
  • TypeORM
  • PostgreSQL

Ответы [ 2 ]

0 голосов
/ 02 ноября 2018

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'])
0 голосов
/ 02 ноября 2018

@ Джонатан Я могу поделиться реализацией, которая у меня есть, и ты видишь, что ты думаешь. Это использует 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 разрешает фактический объект. В этой части вы бы работали более конкретно с вашей БД и тд ... Если не понятно, можете спросить! Надеюсь, это поможет:)

...