В этом сообщении блога я расскажу о теории, реализации и проблемах создания подписок GraphQL с нуля.

Если вы не знакомы с GraphQL, вот пример: GraphQL - это язык запросов для API и среда выполнения для выполнения этих запросов с вашими существующими данными. Исходный код GraphQL был открыт в 2015 году, а операция подписки (добавленная в спецификацию в 2017 году) позволяет вам подписываться на данные в реальном времени в GraphQL. Подписки GraphQL обеспечивают ряд функций на Facebook, в том числе комментарии в реальном времени и потоковое воспроизведение видео в реальном времени. Если вам нужен более подробный обзор GraphQL, посетите graphql.org.

Представьте, что мы создаем почтовый клиент с двумя основными функциями:

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

Для требования №1 клиент может получить соответствующие поля с помощью простого запроса GraphQL:

query FetchEmailsOnStart($viewer: ID!) {
  allEmailsForViewer(viewer: $viewer) {
    receiveTime,
    sender,
    subject
  }
}

В соответствии с требованием № 2 клиент должен запрашивать у сервера уведомление о поступлении нового электронного письма. Например, сервер предоставляет эту функциональность через API pubsub (публикация-подписка):

newEmails.Subscribe(viewerContext, onPublish: (newEmailId) => {
  // execute a GraphQL query to fetch the relevant fields from the new email
});
// Elsewhere in the server-side code, we need to detect the arrival of new emails and publish:
newEmails.Publish(viewerID, newEmailId);

Каждый раз, когда запускается функция обратного вызова onPublish, мы выполняем другой запрос GraphQL, например:

# query to run in response to a new email
query FetchEmailById($viewer: ID!, $newEmailId: ID!) {
  email(viewer: $viewer, id: $newEmailId) {
    receiveTime,
    sender,
    subject
  }
}

Теперь мы можем определить три ключевые обязанности любой реактивной системы GraphQL:

  1. Определите и отслеживайте запрос, который будет запускаться при изменении данных.
  2. Захватите и определите условия, которые могут вызвать повторную оценку запроса.
  3. Переоцените запрос и верните результат.

Что, если бы мы попробовали все это сделать на клиенте? Явно подписываясь на базовый поток исходных событий, клиент содержит императивную логику для того, как обнаруживается «новое событие электронной почты» (отдельное событие pubsub). Сейчас это не большая проблема, но представьте, что этот код находится внутри мобильного приложения, где небольшой процент пользователей никогда не обновляется с этой версии. Если императивные изменения логики включают несколько событий pubsub или другое событие pubsub, эти клиенты могут быть легко сломаны.

Что, если мы перенесем обязанности на сервер? Другими словами, клиент может отправить серверу документ GraphQL. Тогда сервер:

  1. Сохранять и отслеживать документ GraphQL
  2. Захватить условия триггера (исходный поток событий)
  3. Переоцените запрос и верните результат.
  4. Поддерживайте постоянный канал связи с клиентом и возвращайте результаты.

При выполнении этих операций на сервере детали шага 2 скрываются от клиента и могут быть изменены на сервере, а запрос становится полностью декларативным:

subscription SubscribeToNewEmails($viewer: ID!) {
  newEmail(viewer: $viewer) {
    receiveTime,
    sender,
    subject
  }
}

По сравнению с решением на стороне клиента, мы сохраняем круговой обход сети, избегаем раскрытия потока исходных событий на серверной части и устраняем необходимость указывать newEmailId. Клиент по-прежнему знает почему данные изменились, но почему (пришло новое электронное письмо) не связано с как (событие pubsub, называемое newEmail, содержащее newEmailId) .

Масштабирование и операции

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

  • Как система будет масштабироваться для обработки миллионов одновременных пользователей?
  • N-квадратное разветвление: что происходит, когда в чате миллион участников, и все пишут одновременно?
  • Какие виды гарантий мы можем предоставить в отношении доставки на заказ, единовременно? Какого рода компромисс между задержкой и доступностью?
  • Как измерить надежность системы?
  • Как мы переносим клиентские соединения с одного узла на другой во время развертывания?
  • Как система справляется с перегрузкой на одном узле?
  • Должны ли уровни с отслеживанием состояния и без отслеживания состояния быть интегрированы или разделены?

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

Открытый исходный код

Сообщество проявляет постоянный интерес к подпискам GraphQL, поэтому в прошлом году мы добавили его в Спецификацию GraphQL. Вот текст спецификации:

Если операция является подпиской, результатом является поток событий, называемый «потоком ответов», где каждое событие в потоке событий является результатом выполнения операции для каждого нового события в базовом «исходном потоке».

Выполнение подписки создает на сервере постоянную функцию, которая сопоставляет базовый исходный поток с возвращаемым потоком ответа.

Если сопоставить это с нашим примером, Исходный поток - это событие pubsub newEmails. Постоянная функция на сервере запоминает запрос GraphQL и прослушивает исходный поток на предмет событий. Каждый раз, когда приходит новое электронное письмо, функция сопоставления выполняет сохраненный документ GraphQL, используя входные данные из события Source Stream.

Подписки GraphQL доступны у ключевых партнеров сообщества, таких как Apollo и Prisma, но я надеюсь, что этот блог предоставил вам знания для создания собственной реализации подписки GraphQL!