На прошлой неделе в моем посте о преобразовании Discourse API в GraphQL я обещал на этой неделе часть 2. Но сначала мы собираемся сделать крюк и обдумать один ответ на вопрос: как выглядит кэширование результата GraphQL на клиенте? Как это относится к подобным системам, таким как Redux и Meteor?

Добро пожаловать в Building Apollo, публикацию, в которой мы делимся тем, что узнали обо всем, что связано с GraphQL, работая над Apollo: стеком данных для современных приложений.

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

  1. Уменьшение объема загружаемых данных за счет использования данных, которые у нас уже есть на клиенте
  2. Повторная выборка данных, которые могли измениться, когда мы изменяли данные на стороне сервера
  3. Оптимистичный пользовательский интерфейс, работая с кешем напрямую, пока мы ждем (2)
  4. Предварительная загрузка данных, которые могут понадобиться позже

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

Вариант 1: кешировать весь результат запроса

Напомним, как выглядят запрос и ответ GraphQL:

// The query
query {
  item(id: 1) {
    id
    stringField
    numberField
    nestedObj {
      id
      stringField
      numberField
    }
  }
}
// The result
{
  item: {
    id: '5',
    stringField: 'Hello',
    numberField: 6,
    nestedObj: {
      id: '7',
      stringField: 'World',
      numberField: 99
    }
  }
}

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

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

Этим достигается цель 1, потому что мы загружаем меньше данных, чем раньше. А как насчет остальных - повторной загрузки после мутации, оптимистичного пользовательского интерфейса и предварительной загрузки?

Повторное получение после мутации

Допустим, мы произвели мутацию, которая, как мы ожидаем, приведет к мутации вложенного объекта в результате, с идентификатором «7». Как бы мы обновили только эту часть нашего дерева объектов?

Придется работать в обратном направлении и каким-то образом использовать исходный запрос или выполнить новый запрос и исправить это в ответ. Разве не было бы неплохо, если бы мы могли просто войти и сказать «обновить поле« stringField »на объекте с идентификатором 7»? То же самое и с оптимистичным пользовательским интерфейсом, где, вероятно, проще всего думать о полях конкретных объектов. Что, если наш кеш позволит нам это сделать? Что ж, может быть.

Вариант 2: кэшировать объекты и поля

На сервере объекты GraphQL определяются с помощью GraphQLObjectType. У них есть определенный набор полей. Если следовать спецификации Relay Object Identification, у них даже есть идентификаторы. Работа с подобными объектами намного полезнее, чем просто иметь дерево JSON в форме капли!

Давайте сделаем базовый набросок того, как может выглядеть кеш, в котором хранятся объекты и поля:

{
  '5': {
    id: '5',
    stringField: 'Hello',
    numberField: 6,
    nestedObj: '7'
  },
  '7': {
    id: '7',
    stringField: 'World',
    numberField: 99
  }
}

Оказывается, он содержит всю информацию, которая нам нужна для восстановления исходного результата по запросу! У него также есть несколько хороших свойств:

  1. Интегрировать в этот кеш результаты новых запросов, например, результат повторной выборки объекта после мутации, тривиально.
  2. Оптимистичное обновление этих данных просто - вы просто указываете, какой объект вы хотите временно изменить, и какие поля вы хотите установить для этого объекта. Формат кеша достаточно прост, чтобы понять его напрямую, не требуя специальных инструментов для управления им.

Уровень техники для нормализованных хранилищ: Relay, Redux и Meteor

Давайте проясним - я не придумал эту концепцию, это напрямую связано с тем, как работает магазин Relay, как описано в этой захватывающей статье Хьюи Петерсена.

Это также очень похоже на рекомендацию из Redux docs:

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

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

Реализация

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

Он использует структуру кеша, очень похожую на приведенную выше, и сохраняет данные кеша в хранилище Redux, чтобы получить полезные функции, такие как инструменты разработки redux, путешествия во времени и оптимистичный пользовательский интерфейс через библиотеку транзакций, такую ​​как Redux-Optimist.

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

Вот быстрый беглый взгляд на прессы (буквально вчера это заработало!) Того, как это выглядит в Redux devtools, когда с сервера приходит результат запроса:

Мы работаем над тем, чтобы как можно скорее создать сквозную демонстрацию реального приложения, созданного с помощью наших инструментов - ждите! Я знаю, что некоторые из вас задаются вопросом, где взять сервер GraphQL; это вторая половина того, чем мы занимаемся.

Увидимся в следующий раз на Building Apollo!