Skip to content

Да, наконец-то техническая статья 😃

Резолверы это ядро языка запросов, о котором пойдёт речь.

Сначала пара слов о том, что такое 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
        }
    }
}

Запрос при этом проходит три фазы:

  1. построение абстрактного синтаксического дерева (AST)
  2. валидация AST на соответствие каталогу типов (schema)
  3. исполнение резолверов полей для формирования результата в 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