Appearance
Да, наконец-то техническая статья 😃
Резолверы это ядро языка запросов, о котором пойдёт речь.
Сначала пара слов о том, что такое GraphQL и зачем он понадобился.
Когда микросервисы вошли в обиход любого мало-мальски серьезного проекта, собирать данные на клиенте - делая несколько запросов - стало большой болью фронтенд-разработчиков. Несмотря на то, что мы уже активно пользуемся null-safe navigation operator (?.) для предотвращения падений из-за отсутствия каких-либо данных, приходящая с сервера модель может отличаться от контрактной, да и собирать запросы в кучу через Promise.all или RxJS тоже требует особого образования и мышления.
Сначала мы ввели понятие BFF (Backend-for-Frontend) и его разновидность Backend-in-the-Frontend. Суть в том, чтобы агрегировать запросы в модели данных, необходимые клиенту, но в случае REST API, запросы все-равно имели место быть неудобными и деревянными. И нужно было пилить собственный json-schema валидатор чтобы быть уверенным что данные соответствуют контракту.
GraphQL решает эту задачу наилучшим образом, позволяя клиенту делать запросы вида:
query Category {
category(id:12) {
products(limit:3) {
name
price
photoUrl
}
subCategories {
name
photoUrl
}
}
}Запрос при этом проходит три фазы:
- построение абстрактного синтаксического дерева (AST)
- валидация AST на соответствие каталогу типов (schema)
- исполнение резолверов полей для формирования результата в JSON-формате (автору JSON нравится когда это произносят с французским прононсом)
Резолверы работают следующим образом:
- запросы на одном уровне дерева исполняются в параллели
- если запрос асинхронный, его потомки ждут пока он "зарезолвится"
- поскольку запросы выполняются в параллели, лучше всего если они атомарны, идемпотентны (один и тот же запрос приводит к одинаковым результатам) и без сайд-эффектов (нетоксичными 😃)
Сигнатура метода резолвера проста:
js
fieldName(parent, args, context, info) { result }Где,
- parent — данные родительского резолвера,
- args — параметры, переданные в query,
- context — объект, который предоставлен всем резолвером, который можно менять (но не стоит), он чистится между запросами, в нём принято хранить данные аутентификации и авторизации, модели, фетчеры, данные запросов. Не стоит использовать его для кеширования
- info — редко используемый объект, специфичная информация для поля
Необязательно создавать резолвер на каждое поле, достаточно указать корневой, остальные подставятся сами.
Несмотря на очевидное преимущество перед REST API в удобстве использования, GraphQL имеет несколько недостатков.
GraphQL не даёт делать рекурсивные запросы (надеюсь, не нужно объяснять почему, это скорее фича, а не баг), каждое вложенное поле нужно описывать явно, ограничивая дерево запросов.
В случае когда корень дерева содержит множество элементов, и вы запрашиваете поле внутри каждого из них, которое тоже является отдельным запросом, вы рискуете сделать сотни запросов. Это называется проблемой N+1. Проще говоря, это проблема "batch" запросов.Каждый случай индивидуален, но для решения вполне можно использовать context для кеширования промежуточных данных.
Источник вдохновения: https://medium.com/paypal-engineering/graphql-resolvers-best-practices-cd36fdbcef55