Объяснение для различных реализаций функции резольвера в graphql - PullRequest
1 голос
/ 25 апреля 2019

Я читал документы graphQL и обнаружил, что они объяснили реализацию сервера graphql двумя способами: один использует graphql-yoga, который является полнофункциональным сервером graphql, а другой использует graphql, express- graphql и экспресс. В обоих случаях мы передаем функции схемы и распознавателя при создании экземпляра сервера.

Но реализация функции распознавателя отличается. При использовании graphql-yoga функция распознавателя имеет 4 аргумента, которые содержат информацию о родительском объекте, полученных аргументах, контексте, информации. в то время как в другом случае (с использованием graphql) функция распознавателя получает только объект arguments.

Почему это так? Если мне нужна информация, объекты контекста, как мне ее получить?

Используя пример graphql-yoga: https://graphql.org/learn/execution/

Используя пример graphql: https://graphql.github.io/graphql-js/mutations-and-input-types/

// Пример кода с использованием graphql

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');

var schema = buildSchema(`
type Query {
    rollDice(numDice: Int!, numSides: Int): [Int]
}
type Mutation {
    addDice(numDice: Int): String
}
`);

var root = {
    rollDice({numDice, numSides}) {
        return [1, 2];
    },
    addDice({numDice}) {
        console.log("Adding something");
        return "Added";
    }
};

var app = express();
app.use('/graphql', graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

// Пример кода с использованием graphql-yoga

let graphqlServer = require("graphql-yoga");

const typeDefs = `
    type Query {
        rollDice(numDice: Int!, numSides: Int): [Int]
    }
    type Mutation {
        addDice(numDice: Int): String
    }
    `;

const resolvers = {
    Query: {
        rollDice(parent, args, context, info) {
            console.log(args.numDice);
            console.log(args.numSides);
            return [1, 2];
        }
    },
    Mutation: {
        addDice(parent, args, context, info) {
            console.log(args.numDice);
            return "Added";
        }
    }
};

const server = new graphqlServer.GraphQLServer({
    typeDefs,
    resolvers
});

server.start(() => {
    console.log("server started on localhost:4000");
});

Разница между этими 2 фрагментами кода:

Функции резолвера присутствуют внутри соответствующих типов (например, Query, Mutation) в одном случае. В другом случае они присутствуют внутри одного корневого объекта. Это означает, что у меня могут быть методы с одинаковыми именами в Query and Mutation в первом случае, тогда как во втором случае это невозможно, поскольку они являются ключами одного объекта, а ключи должны быть уникальными.

Почему это так? Я что-то упускаю? Как детали реализации могут отличаться от одного пакета к другому?

1 Ответ

1 голос
/ 25 апреля 2019

REAL TALK : документы GraphQL.js не так уж хороши.На мой взгляд, им никогда не следовало использовать примеры с buildSchema, потому что это понятно, что приводит к путанице такого рода.

GraphQL.js (то есть пакет graphql) - это реализация JavaScript дляGraphQL.Создание схемы в GraphQL.js выполняется программно, путем создания экземпляра класса GraphQLSchema:

const userType = new GraphQLObjectType({
  name: 'User',
  fields: {
    id: {
      type: GraphQLID,
    },
    email: {
      type: GraphQLString,
    },
  },
});
const queryType = new GraphQLObjectType({
  name: 'Query',
  fields: {
    user: {
      type: userType,
      resolve: () => ({ id: 1, email: 'john.doe@example.com' }),
    },
  },
});
const schema = new GraphQLSchema({
  query: queryType,
})

Если мы распечатаем эту схему на языке определения схемы (SDL), она выглядит следующим образом:

type Query {
  user: User
}

type User {
  id: ID
  email: String
}

Работать с SDL гораздо проще, чем писать весь этот код. Однако GraphQL.js не обеспечивает способ построения полнофункциональной схемы из SDL. Он предоставляет функцию buildSchema, но эта утилита создает схему без каких-либо преобразователей (и ряда других функций, таких как разрешение типа объединения / интерфейса).

Пакет graphql-tools предоставляет функцию makeExecutableSchema, которая позволяет создавать схему из SDL и сопоставление преобразователяобъект.Это то, что используется под капотом apollo-server и graphql-yoga.makeExecutableSchema создает схему из SDL, используя buildSchema, а затем изменяет результирующий объект, добавляя преобразователи в после факта .

В GraphQL.js, функция resolve (или определитель) для поля принимает четыре параметра - родительское значение, аргументы поля, контекст и объект GraphQLResolveInfo.Если мы создаем GraphQLObjectType как userType в приведенном выше примере, это дополнительная функция, которую мы можем предоставить для каждого из полей в нашем объекте.Это та же самая функция , которую вы определяете, когда создаете карту преобразователя для использования с graphql-yoga. Это единственная реализация распознавателя полей.

Так в чем же дело с buildSchema ??

Примеры в документации используют преимущества GraphQL распознаватель полей по умолчанию :

export const defaultFieldResolver: GraphQLFieldResolver<any, *> = function(
  source,
  args,
  contextValue,
  info,
) {
  if (typeof source === 'object' || typeof source === 'function') {
    const property = source[info.fieldName];
    if (typeof property === 'function') {
      return source[info.fieldName](args, contextValue, info);
    }
    return property;
  }
};

Как видите, логика разрешения по умолчанию ищет свойство с тем же именем, что и поле в исходном (родительском) значении.В нашем примере выше, преобразователь user возвращает {id: 1, email: 'john.doe@example.com'} - это значение, в котором поле разрешает .Поле имеет тип User.У нас нет определителя, определенного для нашего поля id, поэтому распознаватель по умолчанию делает свое дело.Поле id преобразуется в 1, поскольку это значение свойства с именем id для родительского объекта, который получает распознаватель.

Однако родительское значение может также бытьфункция вместо объекта.Если это функция, сначала вызывается, а затем используется возвращаемое значение.С чем вызывается функция?Ну, он не может передать ему родительское значение (из-за бесконечной рекурсии), но он может передать ему оставшиеся три параметра (аргументы, контекст и информация).Вот что он делает.

Теперь для магического трюка ??

В нашем примере я могу опустить преобразователь для поля user и передать функциювместо корневого значения.

const root = {
  user: () => ({id: 1, email: 'john.doe@example.com'})
}

Корневой объект - это просто необязательный объект, который передается в качестве родительского значения для распознавателей на корневом уровне (например, типы Query или Mutation).В противном случае эти средства распознавания не будут иметь родительского значения.

Query - это рабочий корневой тип - он служит «точкой входа» для остальной части вашей схемы.Любые поля типа Query будут переданы корневому объекту в качестве родительского значения.Если я опущу распознаватель для поля user, распознаватель по умолчанию будет 1) проверять родительский объект для свойства с тем же именем, 2) находить свойство и определять, что это функция, 3) вызывать функцию, 4)преобразовать поле в возвращаемое значение функции.

TADA !

Однако, поскольку функция вызывается преобразователем по умолчанию и не используется в качестве преобразователяСам он получит только три вышеупомянутых параметра вместо 4.

Это отличный способ обойти невозможность на самом деле предоставлять собственные преобразователи для схемы, но она очень ограничена.Он работает только для корневых типов, поэтому мы не можем аналогичным образом предоставить поддельные распознаватели для полей User или других типов.Мы не можем использовать интерфейсы или объединения в нашей схеме, потому что мы не можем предоставить resolveType функций.И так далее ...

Надеюсь, это обеспечит некоторую ясность.И, надеюсь, мы сможем обновить документы в ближайшем будущем, чтобы избежать всей этой путаницы.

...