- Аллен Лю ([email protected])

Во время моего отпуска по уходу за ребенком этим летом, помимо того, что моя жена была счастлива, меняла подгузники и отрыгивала ребенка, у меня внезапно появилось немного свободного времени, которого у меня обычно не было. (Немного, но немного.) (Шшшш ... не говори моей жене)

Так что я подумал, что могу потратить это время на то, что всегда хотел делать - создать несколько API GraphQL для ClickTime.

Небольшая предыстория…

Несколько лет назад ClickTime начал использовать собственные конечные точки REST API для всего, что мы создали здесь, в ClickTime. Это позволило нам получить очень полный и обширный набор REST API. Однако, поскольку все довольны REST API и ни один из наших клиентов еще не просил GraphQL, ClickTime по-прежнему официально не поддерживает GraphQL.

Тема GraphQL фактически впервые возникла во время первоначального планирования следующей версии мобильного приложения ClickTime. Мы думали, что GraphQL потенциально может помочь уменьшить объем данных, передаваемых между мобильными устройствами и нашими внутренними серверами. Конечно, это потребует от нас создания API-интерфейсов GraphQL до (или, по крайней мере, параллельно) с новым мобильным приложением. Учитывая, что наша первая версия мобильного приложения использовала REST API и, похоже, никогда не имела проблем из-за объема передаваемых данных или из-за производительности сети, мы решили продолжить создание нашего мобильного приложения следующего поколения на существующих REST API с планами по замене API GraphQL после того, как они станут частью основных предложений по интеграции ClickTime.

В любом случае, вернемся к моему отпуску по уходу за ребенком.

Итак, однажды ночью, около часа ночи, я решил попробовать, как быстро я смогу обернуть наши REST API в GraphQL API ...

Я собираюсь показать вам, что я сделал той ночью, создав простую конечную точку ClickTime GraphQL API, которая возвращает несколько основных сущностей ClickTime, таких как ME, company, различные типы time entries, timesheets, _5 _, jobs и tasks. Эти сущности являются одними из основных строительных блоков ClickTime.

Вот результат, который я ожидал бы получить -

Приведенный выше снимок экрана GraphiQL можно рассматривать как снимок данных ClickTime конкретного пользователя (назовем этого пользователя Мэри):

  • имя company Мэри принадлежит.
  • все users, со своими именами и адресами электронной почты в Мэри company, к которым Мэри имеет доступ (в зависимости от ее роли и настроек ее company ).
  • все отправленные timesheets Мэри.
  • все, что ей дала holiday entries компания Мэри.
  • все, что создала time-off entries Мария.
  • и, наконец, все time entries Мэри, созданное за последние 2 месяца, включая все Client имена, Job имена и Task имена.

Если Мэри хочет составить аналогичный набор данных только с помощью API REST ClickTime, потребуется несколько обращений к серверам API REST ClickTime от Мэри -

  • позвоните /me, чтобы получить информацию обо мне и company ID.
  • позвоните /company, чтобы получить company информацию
  • позвоните /users, чтобы получить все users (, к которым у вас есть доступ), принадлежащие company
  • позвоните /me/holidayentries, чтобы получить все holiday entries, которые у меня есть
  • позвоните /me/timesheeets, чтобы получить все отправленные мной timesheets
  • позвоните /me/timeoff , чтобы получить все time-off entries, которые я создал
  • позвоните /me/timeentries?startDate={startDate}&endDate={endDate}, чтобы получить все time entries, которые я создал за этот конкретный период времени.

Помимо необходимости выполнения нескольких вызовов API, каждый из этих вызовов также отвечает дополнительными свойствами, которые Мэри не обязательно нужны (например, адрес электронной почты или сотрудник число в случае конечной точки /me).

Следовательно, нетрудно увидеть, что GraphQL может быть намного более эффективным как с точки зрения памяти, так и с точки зрения задержки в сети, если вы хотите создать клиентское приложение, для которого требуется несколько сущностей, которые могут иметь или не иметь прямые отношения!

А теперь давайте построим эту штуку.

Сначала я возьму в качестве примера объект Client .

Client в ClickTime примерно означает для кого эта работа (часы). А вот Client определение REST API ClickTime -

В GraphQL все объекты ClickTime определены как GraphQLObjectType. Таким образом, схему GraphQL Client можно определить как

-- /src/schemas/clientType.js --
import { GraphQLObjectType, GraphQLString, GraphQLFloat } from 'graphql';
export default new GraphQLObjectType({
    name: 'Client',
    fields: () => ({
        AccountPackageID: {
            type: GraphQLString,
        },
        BillingRate: {
            type: GraphQLFloat,
        },
        ClientNumber: {
            type: GraphQLString,
        },
        ..... // other properties
     })
});

Довольно просто, правда? По сути, это то же определение REST API Client, переведенное в GraphQLObjectType с соответствующими полями .

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

Например, если по какой-то причине я хочу, чтобы Client.Note был обрезан только до первых 5 символов, я могу определить Client как -

-- /src/schemas/clientType.js --
import { GraphQLObjectType, GraphQLString } from 'graphql';
export default new GraphQLObjectType({
    name: 'Client',
    fields: () => ({
        Notes: {
            type: GraphQLString, 
            resolve: c => c.Notes.substring(0, 5)
        },
        ..... // other properties
     })
});

Поскольку функция resolve также является асинхронной функцией, я могу начать делать что-то действительно мощное - например, объединять вызовы других сущностей для представления отношений между сущности, которые могут быть недоступны (или могут быть неочевидны) с помощью REST API.

Давайте посмотрим на пример.

Теперь я перейду к использованию сущности Company, чтобы продемонстрировать свою точку зрения. Сущность Users не входит в состав свойства сущности Company в ClickTime REST API. Поэтому, если Мэри хочет получить список Users, принадлежащих ей Company, ей нужно будет сделать дополнительный запрос к конечной точке /users REST API, а затем связать возвращаемые списки User с объектом Company.

Однако с помощью GraphQL, просто сделав Users полем объекта Company, мы легко обновили схему Company, чтобы отразить Company-Users отношения. Затем, если запрос Мэри включает свойство Users, когда она запрашивает сущность Company, с приведенным ниже кодом, сервер GraphQL автоматически и асинхронно запросит все Users, принадлежащие компании Мэри и вернуть их как часть объекта Company!

-- /src/schemas/companyType.js --
import { GraphQLObjectType, GraphQLString } from 'graphql';
import UserType from './userType';
export default new GraphQLObjectType({
    name: 'Company',
    fields: () => ({
..... // other company properties
/* "Users" is not part of the "Company" properties in the REST API, but we can tie them together easily with GraphQL fields. */
Users: {
            type: new GraphQLList(UserType),
            async resolve(u, args, {req}) {
                const post = await fetch(UsersURL, {
                    headers: {
                        Accept: 'application/json',
                        Authorization: req.headers.Authorization,
                    }, 
                    ...
                });
                
                const data = await post.json();
                return data.data;
            },
        },
    }),
});

Вот и все! Довольно аккуратно, правда?

Теперь, когда мы понимаем, как определяются наши схемы entity, давайте объединим их вместе!

Идите вперед и создайте schema.js, подобный следующему:

-- /src/schema.js --
import { GraphQLObjectType, GraphQLString, GraphQLSchema } from 'graphql';
import fetch from 'node-fetch';
import CompanyType from './schemas/companyType';
const rootQuery = new GraphQLObjectType({
    name: 'RootQuery',
    fields: () => ({
company:{
            type: CompanyType,
            args: {
                auth: {
                    type: GraphQLString,
                },
            },
            async resolve (root, args, {req}) => {
                if (args.auth) {
                    req.headers.Authorization = args.auth;
                }
const post = await fetch(CompanyURL, {
                    headers: {
                        Accept: 'application/json',
                        Authorization: req.headers.Authorization,
                    },
                });
                
                const data = await post.json();
                return data.data;
            }
        },
        client: {...},
        ...,
    }),
});
export default new GraphQLSchema({
    query: rootQuery
});

Теперь обратите внимание на args в поле Company и первую строку кода if (args.auth) { в функции асинхронного разрешения.

Скорее всего, ваши конечные точки REST API также предоставляют возможность фильтровать ответ, предоставляя дополнительные параметры (например, StartDate / EndDate в /me/timeEntries). Вы можете сделать то же самое с GraphQL API аргумент .

Чтобы продемонстрировать, как это работает, я передам токен аутентификации API через args, а не заголовок запроса -

Затем вы можете начать использовать токен аутентификации в функции Resolve, вызвав args.auth!

Наконец, в свой Express 'app.js добавьте следующий код, чтобы запустить все это!

-- /src/app.js --
import express from 'express';
import bodyParser from 'body-parser';
import graphqlHTTP from 'express-graphql';
import schema from './schema';
const app = express();
const port = 5000;
app.use(
    bodyParser.urlencoded({
        extended: true
    })
);
app.use(bodyParser.json());
app.use((req, res) => {
    graphqlHTTP({
        schema: schema,
        graphiql: true,
        context: {req}
    })(req, res);
});
app.listen(port, () => console.log(`ClickTime GraphQL app listening on port ${port}`));

Вот и все! Теперь вы готовы к использованию своих новых API-интерфейсов GraphQL!

Наконец, вы можете протестировать свои GraphQL API с помощью различных инструментов, так же, как вы можете с REST API. Я использую Postman, чтобы помочь мне тестировать эти конечные точки точно так же, как REST API конечные точки.

Просто добавьте GraphQL {query} как часть тела POST и отправьте.

"Вуаля!"

ДАННЫЕ!!!

Надеюсь, это руководство поможет вам начать путь к использованию API-интерфейсов GraphQL, созданных на основе вашего существующего набора API-интерфейсов Rest. Как видите, реальный код GraphQL несколько минимален, так как Rest API по-прежнему берут на себя большую часть тяжелой работы; поэтому помните, что если ваши Rest API имеют ограничения, такие как регулирование, ваш GraphQL API должны учитывать эти ограничения!

Наконец, в тот вечер мне в этом помогло множество замечательных ресурсов в сети. Рекомендую взглянуть на статью Joey Ng'ethe и скачать его пример https://medium.com/@joeynimu/wrapping-a-rest-api-in-graphql-f50c7b9669d5, чтобы получить представление Начните!

Удачи и удачи!