Повышенная безопасность и эффективность вашего API

Обновление: этот пост был первоначально написан Дхайватом Пандьей в феврале 2017 года, но с тех пор мы обновили его (последний раз в декабре 2017 года), чтобы включить последнюю информацию об использовании постоянных запросов с Apollo.

Если вы завсегдатай закусочной, то знаете, что назвать полное название блюда - поступок новичка. Если вы назовете «двойной чизбургер с беконом, картофелем фри и кока-колой» «№ 12», вы сэкономите кучу слов и дадите возможность кассиру более эффективно выполнить ваш заказ.

В GraphQL есть популярная концепция, называемая «постоянные запросы», которая представляет собой то же самое, но для вашего API. Мы создали в Apollo два разных инструмента, чтобы упростить начало работы с постоянными запросами:

  1. Автоматические постоянные запросы - встроенная функция при использовании Apollo Link и Engine, работает с любым клиентом или сервером.
  2. Persistgraphql - библиотека для статического анализа запросов GraphQL во время сборки, использует тесную связь между клиентом и сервером

В этой статье вы узнаете, как работают постоянные запросы и как начать их использовать с помощью persistgraphql.

Если вы хотите узнать больше об использовании автоматических постоянных запросов с Apollo Link и Engine, ознакомьтесь с нашим сообщением в блоге об этом! 🎉

Если вы раньше создавали сервер GraphQL, вы, вероятно, думали об одном или обоих из них:

  1. Ограничение запросов, которые принимает ваш сервер, путем внесения в белый список определенного набора запросов.
  2. Экономия полосы пропускания между клиентом и сервером за счет отправки только идентификатора запроса или хэша вместо всего запроса GraphQL

Что, если бы мы могли решить проблемы белого списка запросов и использования полосы пропускания одним махом? Вот что делает поддержка постоянных запросов! О постоянных запросах много говорилось: в Apollo Client, в express-graphql и в обсуждениях разработчиков в Facebook, и в этом посте мы расскажем вам все, что вам нужно знать о них: как они работают, инструментарий, который мы создали вокруг них, и то, как вы можете включить постоянные запросы в свое приложение GraphQL.

Что такое постоянные запросы?

Допустим, мы пишем код внешнего интерфейса, использующий GraphQL. Мы могли бы написать некоторый код слоя представления, который будет выглядеть так:

import COMMENT_QUERY from ‘../graphql/Comment.graphql’;
const withData = graphql(COMMENT_QUERY, {
 options: ({ params }) => ({
 …
 }),
 …
});

где мы используем загрузчик Webpack graphql-tag / loader для импорта файла .graphql. Когда мы визуализируем этот компонент, Apollo Client запускает запрос, содержащийся в Comment.graphql. Для этого Apollo Client должен отправить сам запрос на сервер в виде строки, и сервер просмотрит запрос, разрешит его и отправит обратно некоторые данные. Все это происходит во время выполнения, что приводит к использованию высокой пропускной способности и неограниченному выполнению запросов.

Но если вы последуете нашему совету, вы уже во время сборки знаете, какой запрос собирается отправить клиент. То есть сам документ запроса находится в файле Comment.graphql еще до того, как клиент обработал его. Таким образом, клиент может просто сказать серверу: Привет, можно мне получить результаты по запросу номер 16? и сервер, который согласовал с клиентом отображение числа в запрос, отправит обратно правильные результаты. Мы экономим некоторую пропускную способность, не отправляя весь документ, и сервер будет разрешать только те запросы, о которых уже известно, предотвращая отправку злоумышленником вредоносных запросов.

Мы используем термин постоянные запросы, чтобы описать это соглашение о сопоставлении числа с запросом. На самом деле он представляет собой двунаправленное отображение между документами GraphQL и сгенерированными идентификаторами. Но для того, чтобы мы могли реализовать поддержку постоянных запросов, нам нужны запросы, которые можно статически анализировать, что означает, что мы должны иметь возможность определять запрос, который отправляет клиент. во время сборки. Apollo Client с самого начала использовал статические запросы, и теперь мы создали инструменты, необходимые для постоянных запросов, чтобы сделать их добавляемыми в ваше приложение.

Как работают постоянные запросы

Клиент

Для поддержки постоянных запросов мы создали persistgraphql, инструмент сборки, который сканирует репозиторий исходного кода вашего клиента, извлекает документы GraphQL, создает сопоставление между документами GraphQL и их сгенерированными идентификаторами и записывает это сопоставление в файл JSON. Он также обеспечивает реализацию сетевого интерфейса для Apollo Client, который принимает все запросы, но отправляет на сервер только идентификатор запроса.

Давайте разберем эти части и посмотрим, как они на самом деле работают, начиная с самой карты JSON. Вы можете представить, что карта выглядит примерно так:

{
 <GraphQL Document AST>: < ID >,
}

К сожалению, ключи в Javascript могут быть только строками или числами. Мы можем решить эту проблему с помощью функции graphql-js print:

{
  <print(GraphQL Document AST) >: < ID >,
}

Это почти правильно, но Apollo Client позволяет применять некоторые статические преобразования к запросам, например, добавлять поле __typename на каждый уровень нашего запроса. Один из наших запросов может содержать следующий документ GraphQL (мы называем его исходным запросом):

query {
  author {
    firstName
    lastName
  } 
}

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

query {
  author {
    firstName
    lastName
    __typename
  }
}

Поскольку мы хотим иметь возможность реализовать поддержку постоянных запросов в Apollo Client путем простой реализации сетевого интерфейса, мы хотим, чтобы наша карта запросов могла сопоставлять эти сетевые запросы и сгенерированные идентификаторы. Итак, наша последняя карта JSON выглядит так:

{
 < print(transform(GraphQL Document)) >: < ID >,
}

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

В сетевом интерфейсе для постоянных запросов структура данных карты, которую выводит инструмент persistgraphql, становится доступной для клиента как часть пакета. Ядро этого сетевого интерфейса состоит из одного метода:

query(request: Request) {
 // look up request.query in the map data structure
 // find the corresponding ID
 // send the query ID to the server, along with the query variables
 // return a Promise for the result returned by the server
}

Поле request.query содержит преобразованный запрос, который должен быть отправлен по сети. Сетевой интерфейс persistgraphql постоянных запросов ищет идентификатор, связанный с request.query в структуре данных карты. Наконец, он отправляет этот идентификатор запроса на сервер вместе с любыми переменными, необходимыми для оценки запроса. Все просто, правда?

Сервер

Вы, наверное, уже догадались, что происходит на сервере. Сервер получит от клиента что-то вроде этого:

{
  id: < query ID >,
  variables: < variables >,
}

и превратите его в это:

{
  query: < print(GraphQL document) >,
  variables: < variables >
}

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

Как это использовать

Достаточно поговорить о внутреннем устройстве; давайте посмотрим, как на самом деле настроить ваш клиент и сервер для использования постоянных запросов. Сначала нам нужно установить persistgraphql:

$ npm install --save persistgraphql

Это дает нам persistgraphql инструмент CLI, а также сетевой интерфейс Apollo Client. Мы можем использовать этот инструмент для создания файла JSON, который содержит сопоставление между запросами и идентификаторами. Нам просто нужно указать persistgraphql на наш исходный каталог внешнего интерфейса, который содержит наши .graphql файлы:

$ persistgraphql ui [--add_typename]

где мы передаем флаг --add_typename, если мы используем преобразователь addTypename в Apollo Client. Если все пойдет хорошо, должен получиться файл с именем extracted_queries.json. В нашем клиентском коде мы должны импортировать этот файл и заменить существующий сетевой интерфейс на PersistedQueryNetworkInterface из persistgraphql/lib/browser. Это может выглядеть так:

import queryMap from ‘./extracted_queries.json’;
import { PersistedQueryNetworkInterface } from ‘persistgraphql’;
const networkInterface = new PersistedQueryNetworkInterface({
  queryMap,
  uri: apiUrl,
  opts: {
    credentials: ‘same-origin’,
    headers,
  },
});

Обратите внимание, что для правильной работы оператора импорта вам необходимо настроить Webpack (или любой другой инструмент сборки, который вы выберете) для правильной обработки файлов JSON; Вы можете ознакомиться с подробностями в нашем примере приложения GitHunt-React. Наконец, на сервере мы должны сделать аналогичный импорт (мы также могли бы использовать API файловой системы Node для загрузки файла JSON) и написать простое промежуточное программное обеспечение для замены идентификаторов входящих запросов на все это:

import queryMap from ‘../extracted_queries.json’;
import { invert } from 'lodash';
app.use(
  '/graphql',
  (req, resp, next) => {
    if (config.persistedQueries) {
      const invertedMap = invert(queryMap);
      req.body.query = invertedMap[req.body.id];
    }
    next();
  },
);

Конечно, есть несколько способов реализовать промежуточное ПО. Здесь мы используем invert из lodash, чтобы инвертировать карту и искать по id, но вы можете представить себе загрузку запросов из базы данных или какого-либо другого механизма хранения.

Вот и все! После внесения этих изменений приложение начнет загружать данные с использованием идентификаторов запросов, а не целых документов запроса.

Почему это важно

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

Что дальше

Постоянные запросы - это лишь первый шаг в направлении более ориентированных на производство функций для Apollo Client. Реализуя постоянные запросы, мы показали, что с Apollo вы можете есть свой пирог и есть его тоже: эти функции обеспечивают отличную производительность и оптимизацию безопасности, не жертвуя фантастическим опытом разработчика, которым известен Apollo. По мере продвижения вперед мы продолжим создавать такие функции, как постоянные запросы, которые делают GraphQL фантастическим выбором как для прототипов, так и для производственных приложений.

Если вы заинтересованы в работе над проектами GraphQL с открытым исходным кодом: мы всегда ищем новых участников. Мы тоже нанимаем!