8/7/2022

Магия декларативности и схемы. Часть 1: Парсим GraphQL запросы

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

Серия будет длинной. Для начала соберем примитивный запрос и скрафтим простую схему, помучаем ее при помощи низкоуровневых апишек, посмотрим на то, что можно делать с подобной схемой. Постепенно (к 6 статье :) мы дойдем до облачных решений, которые значительно упростят вашу жизнь, если вы работаете с GraphQL.

Декларативно и императивно

Мы можем взаимодействовать с внешней системой двумя способами:

  • Императивно – выдать системе инструкции, которые она будет выполнять. Мы будем ожидать, что в результате получится то, что мы хотим.
  • Декларативно – явно сообщить системе, что мы хотим. Система сама определит порядок действий, чтобы выдать нам желаемое.

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

Дальше каким-то образом нужно описать, что мы хотим, на языке, понятном системе. Для этого нам нужен Query Language.

Давайте посмотрим, как это выглядит для GraphQL. Мы будем делать wiki для любителей функционального программирования. Каждая страничка wiki будет описывать какой-нибудь алгебраический тип.

Если вы ничего не знаете о graphql — не беда

Вот тут можно посмотреть слайды моего доклада, в котором я все объясняю. А вот тут сам доклад.

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

Одно из преимуществ GraphQL — его независимость от транспорта. Все что нам нужно – отправить запрос и получить ответ. Нам все равно, каким способом этот ответ будет доставлен. А как с этим обстоят дела у вас? Взаимодействуя с бэкендом, думаете ли вы о транспорте? Есть ли в вашем приложении разделение между логикой взаимодействия с данными и логикой отправки и получения запроса.

Как сделать правильный запрос?

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

  • У нас ошибка в грамматике.
  • С грамматикой все окей, но мы просим то, что система не может нам дать.

Теперь откроем нашу любимую IDE и напишем какой-нибудь GraphQL запрос.

touch validQuery.graphql


Теперь GraphQL execution engine должен выполнить этот запрос. Но есть проблема :)

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

Это легко. Воспользуемся graphql-tag.

Ставим зависимости

yarn add @apollo/client graphql

и натравливаем graphql-tag на наш запрос.

Apollo?

Я буду использовать graphql-tag из библиотеки Apollo, потому что нам еще многое оттуда понадобится. Но вы можете установить graphql-tag отдельно или использовать функцию parse из библиотеки graphql.


Oй, какие симпатичные бэктики!

Ага. Если еще не интересовались, почитайте про tagged template literals.

В объекте query будет лежать результат парсинга. И сейчас будет немножко капитанства про парсеры. Когда вам нужно попарсить какой-то текст, у вас есть множество способов это сделать.

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

  • вам нужна грамматика,
  • если ваш запрос грамматически неверный, парсер упадет.

Именно это делает graphql-tag, вызывая метод parseDocument из библиотеки graphql.

Если вам интересно, как устроен graphql парсер, можете заглянуть вот сюда

Вот что мы получим в результате:


A теперь давайте сломаем наш запрос:


Поймаем ошибку и подробненько ее изучим.



Наш код упал во время парсинга. То же самое сделает graphql engine, если попросить его выполнить грамматически некорректный graphql запрос.

Как импортитровать этот странный graphql?

Обратите внимание: я, особо не заморачиваясь, импортирую .graphql файлики в свой код. Из коробки это работать не будет. В случае моего блога, я читерю, используя raw-loader из webpack. В production вы наверняка захотите делать иначе. Советую глянуть вот сюда, чтобы понять, как прикрутить graphql-tag для правильного импорта graphql запросов.

Вторая проблема возникнет у вас, если вы используете TypeScript. TS ничего не знает о странных модулях с расширением .graphql. Чтобы это починить, создайте в корне вашего проекта файл custom-types.d.ts (если у вас такого еще нет) и добавьте туда

declare module '*.graphql' {
  const contents: string
  export = contents
}

Это сработает для raw-loader. Для graphql-tag/loader вам понадобится заменить string на другой тип.

С парсингом разобрались. Что дальше?

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