Магия декларативности и схемы. Часть 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 запросы.