Пять распространенных проблем в программах GraphQL (и способы их решения)

1656680897 pyat rasprostranennyh problem v programmah graphql i sposoby ih resheniya

Саша Грайф

otGc4zlSYDhfVWzfyANc1ZOX5s3cJbJMF4J7

Научитесь разблокировать мощность GraphQL, не испытывая ее недостатков

GraphQL сегодня в моде, и не зря: это элегантный подход, решающий многие проблемы, связанные с традиционными REST API.

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

Я говорю о таких проблемах, как:

  • Дублирование схемы
  • Несоответствие данных серверу/клиенту
  • Лишние вызовы базы данных
  • Низкая производительность
  • Шаблонная передозировка

Я готов поспорить, что ваше приложение страдает по крайней мере от одного из них. Хорошая новость: ни один из них неизлечим!

Для каждой проблемы я опишу проблему, а затем объясню, как я решаю ее в Vulcan, фреймворке с открытым исходным кодом React/GraphQL, над которым работал в течение последнего года (вы должны проверить это!). Но, надеюсь, вы сможете применить те же стратегии к собственной кодовой базе, независимо от того, используете ли вы Vulcan или нет.

WzpNm95jVV1teXE08i6UcUQ3UmjxuQrX7d8E

Проблема: Дублирование схемы

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

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

Решение: генерация схемы GraphQL

В экосистеме GraphQL появился ряд решений этой проблемы. Например, PostGraphile генерирует схему GraphQL из базы данных PostgreSQL, а Prisma также поможет вам генерировать типы для ваших запросов и изменений.

Я также помню, как Лейни Замор и Адам Крамер из команды GraphQL описывали, как они непосредственно генерировали свою схему GraphQL из определений типов PHP.

Для Vulcan я самостоятельно наткнулся на очень похожее решение. Я использовал SimpleSchema, чтобы описать свои схемы как объекты JavaScript, и начал просто с преобразования JavaScript String введите в GraphQL String, Number в Int или Floatи так дальше.

Итак, это поле JavaScript:

title: {  type: String}

Станет этим полем GraphQL:

title: String

Но, конечно, схема GraphQL также может иметь собственные типы: User, Comment, Eventи так дальше.

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

userId{  type: String,  resolveAs: {    fieldName: 'user',    type: 'User',    resolver: document => {      return Users.findOne(document.userId)    }  }}

Становится:

user: User

Как видите, мы также определяем фактическую функцию резольвера в поле, поскольку она также напрямую связана с полем GraphQL.

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

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

czkjIXuQcTg3x1rNV41WWK8U7BWTiF-UV5xm

Проблема: несоответствие данных сервера/клиента

Как мы только что видели, ваша база данных и GraphQL API будут иметь разные схемы, которые превращаются в разные формы документов.

Так что пока а post только из базы данных будет иметь a userId собственность, то же post как полученное через ваш API, будет иметь a user собственность.

Это означает, что получение имени автора публикации на клиенте будет выглядеть так:

const getPostNameClient = post => {  return post.user.name}

Но на сервере это будет совсем другая история:

const getPostNameServer = post => {  const postAuthor = Users.findOne(post.userId)  return postAuthor.name}

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

Недавно я почувствовал эту боль, когда пытался построить систему для создания еженедельных информационных бюллетеней: каждый информационный бюллетень состоял из нескольких публикаций и комментариев вместе с информацией об их авторах; иными словами, идеальный вариант использования GraphQL. Но это создание информационного бюллетеня происходило на серверчто означает, что у меня не было способа спросить конечную точку GraphQL…

Решение: межсерверные запросы GraphQL

Или я? Оказывается, вы можете выполнять межсерверные запросы GraphQL просто замечательно! Просто передайте свою схему исполняемому файлу GraphQL в graphql вместе с вашим запросом GraphQL:

const result = await graphql(executableSchema, query, {}, context, variables);

В Vulcan я обобщил этот шаблон в a runQuery помощник, и я тоже добавил queryOne функции для каждой коллекции Они действуют так же, как MongoDB findOne за исключением того, что они возвращают документ, полученный через API GraphQL:

const user = await Users.queryOne(userId, {  fragmentText: `    fragment UserFragment on User {      _id      username      createdAt      posts{        _id        title      }    }  `});

Межсерверные запросы GraphQL помогли мне упростить мой код. Это позволило мне изменить мой вызов создания информационного бюллетеня по беспорядку последовательных вызовов базы данных и циклов к одному запросу GraphQL:

query NewsletterQuery($terms: JSON){  SiteData{    title  }  PostsList(terms: $terms){    _id    title    url    pageUrl    linkUrl    domain    htmlBody    thumbnailUrl    commentsCount    postedAtFormatted    user{      pageUrl      displayName    }    comments(limit: 3){      user{        displayName        avatarUrl        pageUrl      }      htmlBody      postedAt    }  }}

Вывод: не принимайте GraphQL как чистый протокол клиент-сервер. GraphQL можно использовать для запроса данных в любой ситуации, включая клиент-клиент с помощью Apollo Link State или даже во время статической сборки с помощью Gatsby.

FuGWs40558hc3tICTQLvg0tjEZC1B3CsngvM

Проблема: лишние вызовы базы данных

Представьте список публикаций, к каждой из которых прикреплен пользователь. Теперь вы хотите показать 10 из этих публикаций вместе с именем их автора.

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

Но как насчет GraphQL? Если предположить, что наши публикации имеют a user поле с собственным резолвером, мы все еще имеем один начальный вызов базы данных, чтобы получить список сообщений. Но теперь у нас есть дополнительный звонок для каждого пользователя на резольверна общую сумму 11 вызовы базы данных!

А теперь представьте, что каждый пост также имеет 5 комментариев, каждый из которых имеет автор. Сейчас количество наших звонков выросло до:

  • 1 для списка постов
  • 10 для авторов публикации
  • 10 для каждого подписка из 5 комментариев
  • 50 для авторов комментариев

На общую сумму 71 вызовы базы данных для отображения единственный вид!

Никто не хочет объяснять своему начальнику, почему домашняя страница загружается 25 секунд. К счастью, есть решение: Dataloader.

Решение: загрузчик данных

Dataloader позволит вам пакетно и кэшировать вызовы базы данных.

  • Дозировка означает, что если Dataloader обнаружит, что вы нажимаете ту же таблицу базы данных несколько раз, он объединит все вызовы вместе. В нашем примере вызовы 10 авторов сообщений и 50 авторов комментариев будут объединены в один вызов.
  • Кэширование означает, что если Dataloader обнаружит, что два сообщения (или сообщение и комментарий) имеют одного автора, он повторно использует уже имеющийся в памяти объект пользователя вместо того, чтобы совершать новый вызов базы данных.

На практике вы не всегда достигаете идеального кэширования и пакетирования, но Dataloader все равно огромная помощь.

А Vulcan упрощает использование Dataloader. Из коробки любая модель Vulcan содержит функции Dataloader как альтернативу «обычным» функциям запросов MongoDB.

Да в дополнение к collection.findOne и collection.findвы можете использовать collection.loader.load и collection.loader.loadMany.

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

GxlQow63LtMbXmI1doulB29kJ9Bvpfmg3DbZ

Проблема: низкая производительность

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

Это может огорчать: с одной стороны, вы хотите полностью воспользоваться функциями обхода графов GraphQL («покажи мне авторов комментариев автора публикации…»). Но, с другой стороны, вы не хотите, чтобы ваша программа стала медленной и не реагировала.

Решение: Кэширование запросов

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

Apollo Engine – отличный способ сделать это. Это размещенная служба, которая предоставляет аналитику ваших запросов GraphQL, но также имеет очень полезную функцию кэширования.

Vulcan имеет встроенную интеграцию Engine, вам просто нужно добавить ключ API в файл настроек. Затем вы можете добавить enableCache: true аргумент для ваших запросов GraphQL, чтобы кэшировать их с помощью Engine.

Или с помощью встроенных компонентов высшего порядка для загрузки данных Vulcan:

withList({  collection: Posts,   enableCache: true})(PostsList)

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

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

biuavoAHWIt5n3bDypomKTulQZywt2NZyN0L

Проблема: шаблонная передозировка

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

Как правило, добавление новой модели (например, Comments) в вашу программу будет включать следующие шаги:

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

Это очень много CRUD!

Решения: общие резольверы, мутации и компоненты высшего порядка

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

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

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

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

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

К счастью, все эти проблемы имеют решение, и чем больше мы их обсуждаем (кстати, Vulcan Slack — отличное место для этого!), тем лучше эти решения станут!

Добавить комментарий

Ваш адрес email не будет опубликован.