Это часть 1, вы также можете найти часть 2 здесь или часть 3 здесь.

Недавно мы решили обновить один из наших микросервисов с помощью GraphQL. Приложение находится между нашим новым мобильным веб-клиентом и нашим сервисом Discovery API. Цель приложения - моделировать наши данные и повышать производительность за счет управления количеством обращений к сервису, которые клиент должен сделать во внешнем интерфейсе. Мы используем пограничное кэширование, чтобы избежать ненужных обращений к серверу, позволяя перехватывать запросы и раньше возвращать их из памяти. Мы были хорошо осведомлены о запросах, которые клиент будет отправлять заранее, поэтому мы также хотели использовать сохраненные запросы GraphQL в попытке сэкономить полосу пропускания между клиентом и сервером. Поскольку GraphQL отправляет запросы POST в одну конечную точку, кэширование на границе становится затруднительным. Здесь нам может помочь запрос GET. В этом руководстве мы создадим сервер GraphQL, который принимает запросы GET для постоянных запросов.

ОБНОВЛЕНИЕ: мир технологий быстро развивается. Эта серия руководств начинается с Apollo Server 1.3.2, но не беспокойтесь, в третьей части мы рассмотрим Apollo Server 2 и весь процесс миграции. А пока, если у вас возникнут какие-либо проблемы, вы можете скачать исходные файлы проекта здесь: исходные файлы.

Для начала создайте рабочий каталог и установите следующие пакеты с помощью npm.

npm init -y &&
npm install --save graphql graphql-tools apollo-server-express express graphql-playground-middleware-express body-parser ramda

Нам потребуется создать файл схемы и файл преобразователей.

touch schemas.js resolvers.js

schemas.js

const Greeting = `
  type Greeting {
    name: String
    text: String
  }
`
const Query = `
  type Query {
    greeting(name: String): Greeting
  }
`
module.exports = [Greeting, Query]

resolvers.js

const resolvers = {
  Query: {
    greeting: (_, { name }) => ({ 
      name,
      text: 'How are you today?'
    })
  }
}
module.exports = resolvers

Далее нам нужно создать извлеченный файл запросов. Это будет использоваться в качестве хэш-карты для клиента. Вместо того, чтобы отправлять весь документ запроса, мы позволим клиенту отправлять параметры запроса. Параметры запроса будут содержать ключ, называемый хешем, значение которого будет соответствовать ключу в нашем извлеченном файле запросов. Другими словами, вместо того, чтобы заказывать гамбургер, картофель фри и напиток, наш клиент просто заказывает номер 1. { hash: 1 }

touch extracted_queries.js

Внутри файла extract_queries.js мы экспортируем объект. Ключом будет хэш (в нашем случае простое целое число), а значением будет желаемый запрос.

extract_queries.js

module.exports = {
 1: `query Greeting($name: String!) {
      greeting(name: $name) { 
       name
       text   
      }
    }` 
}

Затем нам нужно будет создать промежуточное программное обеспечение, чтобы проверять все входящие запросы на наличие хэша в параметрах запроса.

touch persistedQueries.js

Если хеш существует, мы получим соответствующий ключ в нашем извлеченном файле запросов. Если его нет, мы отправим запрос без изменений.

persistedQueries.js

const { omit } = require('ramda')
const queryMap = require('./extracted_queries.js')
const persistedQueries = (req, res, next) => {
  const { hash = '' } = req.query
  
  if (!hash) return next()
  const query = queryMap[hash]
  
  if (!query) {
    res.status(400).json({ error: [{}] })
    return next(new Error('Invalid query hash'))
  }
  req.query = {
    query,
    variables: omit(['hash'], req.query)
  }
  next()
}
module.exports = persistedQueries

В функции persistedQueries мы начинаем с деструктуризации хэш-свойства объекта req.query, устанавливая в качестве значения по умолчанию пустую строку. Затем мы проверяем истинное значение в хэш-опоре, если она не указана, мы просто возвращаем next(), отправляя запрос следующей следующей функции промежуточного программного обеспечения.

Если хэш существует, мы используем его для сопоставления с нашим извлеченным файлом запросов. Мы переназначаем объекту req.query соответствующий запрос и используем метод omit ramda, чтобы удалить хеш-ключ перед вызовом next(), отправив запрос по пути. Если совпадения не существует, мы возвращаем статус 400 и вызываем next(...), передавая сообщение об ошибке, которое позже будет зарегистрировано и просмотрено. Теперь все, что нам нужно сделать, это создать файл server.js, чтобы связать все это вместе.

touch server.js

server.js

const express = require('express')
const bodyParser = require('body-parser') 
const playground = require('graphql-playground-middleware-express').default
const { graphqlExpress, graphiqlExpress } = require('apollo-server-express')
const { makeExecutableSchema } = require('graphql-tools')
const persistedQueries = require('./persistedQueries')
const typeDefs = require('./schemas')
const resolvers = require('./resolvers')
const port = 4000
const app = express()
const schema = makeExecutableSchema({ typeDefs, resolvers })
app.use(
  '/graphql', 
  bodyParser.json(),
  persistedQueries,
  graphqlExpress({ schema })
)
app.use(
  '/playground', 
  playground({ endpoint: '/graphql' })
)
app.listen(port, () => console.log(`listening on port: ${port}`))

Мы создаем простое экспресс-приложение и объединяем наши typeDefs и резолверы с помощью функции makeExecutableSchema graphql-tools. Мы используем express app.use method для объявления промежуточного программного обеспечения на основе маршрута. Первый аргумент - это строка, содержащая желаемый маршрут /graphql. Чтобы поддерживать обратную совместимость, мы передаем промежуточное ПО body-parser, которое нам понадобится для любых запросов POST, которые может получить сервер. Затем мы передаем наше persistedQueriesmiddleware, которое будет вносить изменения в запрос, только если обнаружит хэш в параметрах req.query. Последний параметр - это промежуточное ПО graphqlExpress от apollo-server-express. Это принимает объект с объединенными результатами нашего makeExecutableSchema вызова выше.

Также мы создаем маршрут для GraphQL в браузерной IDE GraphQL Playground. Мы будем использовать это для тестирования наших маршрутов POST, чтобы убедиться, что мы не нарушили функциональность сервера GraphQL по умолчанию.

Теперь давайте запустим сервер с помощью следующей команды.

node server.js

Если вы видите сообщение listening on port: 4000 в терминале, значит, все готово. Откройте браузер и перейдите к http://localhost:4000/playground.. Вы должны увидеть среду IDE graphql-playsky. Скопируйте и вставьте запрос из нашего извлеченного файла запросов в graphql-Playground и запустите запрос, щелкнув значок воспроизведения. Не забудьте добавить переменную name в раздел переменных внизу, вот так…

Если все сделано правильно, вы должны увидеть результаты своего запроса в виде ответа JSON.

{
  "data": {
    "greeting": {
      "name": "Ticketmaster",
      "text": "How are you today?"
    }
  }
}

Теперь откройте новую вкладку в браузере и перейдите к http://localhost:4000/graphql?hash=1&name=Ticketmaster.. Вы должны увидеть тот же ответ JSON, что и на предыдущей вкладке, но на этот раз мы используем параметры запроса, а не отправляем на сервер весь документ запроса.

Успех! Теперь мы получаем повышение производительности за счет наших постоянных запросов GraphQL и краевого кэширования из наших URI запросов GET.

Дополнительные материалы для чтения

Мы размахивали руками над множеством разных пакетов, которые делают много крутых вещей. Вот несколько ссылок на несколько почетных упоминаний