Фронтенд-команда просит «отдайте товар сразу с ценами, остатками, картинками и разделом одним запросом», мобильное приложение жалуется на медленную загрузку карточки, а бэкенд каждый раз лепит новый REST-метод под очередной экран. Знакомая картина для магазина на Битриксе, который перерос классическую серверную вёрстку и оброс SPA-витриной и приложением. Ответ на этот запрос — GraphQL-слой поверх данных 1С-Битрикс: единая точка входа, где клиент сам описывает нужные поля, а сервер собирает ответ из инфоблоков, торгового каталога и заказов.
Ниже — практический разбор того, как спроектировать и внедрить GraphQL над Битриксом, не переписывая ядро: как устроена схема, как писать резолверы на D7, как убить проблему N+1, кэшировать ответы и защитить endpoint от тяжёлых запросов. Это выжимка из наших проектов по разработке на 1С-Битрикс — с прицелом на реальную нагрузку, а не на демо из документации.
Коротко
- GraphQL — это отдельный endpoint поверх Битрикса, а не замена ядра: витрина работает как прежде.
- Клиент запрашивает ровно нужные поля одним запросом — меньше трафика и меньше обращений, чем у REST.
- Главный технический враг — проблема N+1; лечится паттерном DataLoader (пакетная загрузка).
- Кэшируйте на уровне резолверов через тегированный кэш Битрикса, а не тело запроса целиком.
- Обязательная защита: лимит глубины, стоимость запроса (complexity), тайм-аут и персистентные запросы.
Что такое GraphQL и зачем он поверх Битрикса
GraphQL — это язык запросов к данным и среда их выполнения на сервере. В отличие от привычного REST, где каждый ресурс живёт по своему URL и возвращает фиксированный набор полей, у GraphQL одна точка входа (обычно /graphql), а форму ответа задаёт сам клиент. Он присылает запрос, описывающий дерево нужных полей, а сервер возвращает JSON ровно той же формы — ни больше, ни меньше.
Для магазина на 1С-Битрикс это решает застарелую боль. Данные магазина живут в инфоблоках (каталог, разделы, свойства), в модуле торгового каталога (цены, остатки, торговые предложения), в модуле заказов и в профилях пользователей. Классическая серверная вёрстка собирает всё это на PHP и отдаёт готовый HTML. Но как только появляется SPA-витрина на Vue или React, а следом мобильное приложение, возникает вопрос: как отдать этим клиентам данные удобно и экономно? Плодить десятки узкоспециализированных REST-методов — путь в тупик поддержки.
GraphQL-слой становится «фасадом» над всеми этими источниками. Он не хранит данные сам — он знает, как достать их из Битрикса через D7 API, и предоставляет клиенту единый типизированный контракт. Один экран приложения запрашивает у товара название и цену, другой — характеристики и отзывы, третий — остатки по складам, и всё это без единой правки на бэкенде, потому что схема уже описывает все доступные поля.
Важно сразу очертить границы применимости. GraphQL — не «ускоритель сайта» и не замена кэшу. Это способ организовать доступ к данным для клиентских приложений. Если у вас классический сайт на серверной вёрстке без SPA и мобильных клиентов, отдельный GraphQL-слой скорее избыточен. Он раскрывается там, где есть несколько разных потребителей одних и тех же данных с разными требованиями к их форме.
GraphQL против REST: в чём разница
Чтобы решение было осознанным, полезно честно сравнить два подхода на языке магазина. И REST, и GraphQL в экосистеме Битрикс вполне жизнеспособны — вопрос в сценарии. О REST-подходе в Битриксе у нас есть отдельный разбор про REST API, вебхуки и приложения; здесь же сфокусируемся на отличиях с точки зрения фронтенда.
| Критерий | REST | GraphQL |
|---|---|---|
| Форма ответа | Фиксирована сервером на каждый ресурс | Определяется клиентом в запросе |
| Число запросов на экран | Часто несколько (товар, цены, остатки отдельно) | Обычно один запрос с вложенностью |
| Лишние данные (over-fetching) | Частая проблема | Отсутствуют — берётся только нужное |
| Нехватка данных (under-fetching) | Требует новых методов | Решается расширением запроса |
| Кэширование по URL | Простое, из коробки | Сложнее: нужен кэш резолверов или персист-запросы |
| Контракт и типизация | По документации / OpenAPI | Строгая схема как единый источник правды |
| Порог входа | Низкий | Выше: схема, резолверы, защита |
Ключевой практический выигрыш GraphQL — устранение over-fetching и under-fetching. Представьте карточку товара в мобильном приложении: ей нужны название, одна картинка, цена и признак наличия. REST-метод catalog.product.get вернёт всю модель товара со всеми свойствами — десятки лишних полей, которые приложение выбросит. GraphQL отдаст ровно четыре запрошенных поля. На каталоге, где такие карточки грузятся десятками, экономия трафика и времени рендера становится заметной.
Обратная сторона — цена входа. GraphQL требует спроектировать схему, написать и защитить резолверы, продумать кэш. Это инженерная работа, которая окупается на нескольких клиентах и большом каталоге, но избыточна для маленького сайта с одной серверной витриной. Решение о переходе — это всегда взвешивание: сколько у вас потребителей данных и насколько разные у них требования.
Архитектура GraphQL-слоя над Битрикс
GraphQL-слой удобно представлять как несколько ответственностей, выстроенных в цепочку. Понимание этой структуры сразу подсказывает, где живёт та или иная логика и где искать проблему при отладке.
- Точка входа (endpoint). Один URL, принимающий POST с телом запроса. В Битриксе это может быть отдельный публичный файл-обработчик или маршрут в составе своего модуля.
- Схема (schema). Типизированное описание всех доступных данных: типы, поля, запросы (Query) и изменения (Mutation). Единый контракт между клиентом и сервером.
- Резолверы (resolvers). Функции, которые для каждого поля знают, как достать его значение из Битрикса — через D7 API инфоблоков, каталога, заказов.
- Слой источников данных. Обёртки над API Битрикса с кэшем и пакетной загрузкой (DataLoader), чтобы не бить по базе на каждое поле.
- Слой безопасности. Валидация запроса: глубина, стоимость, тайм-аут, авторизация полей, персистентные запросы.
- Контекст запроса. Объект с текущим пользователем, правами и разделяемыми на запрос загрузчиками, который прокидывается во все резолверы.
Технически на PHP endpoint чаще всего собирают на библиотеке webonyx/graphql-php — это де-факто стандарт для GraphQL-сервера на PHP, совместимый с окружением Битрикса. Она отвечает за парсинг запроса, валидацию по схеме и обход дерева резолверов. Всё, что касается данных, вы пишете сами поверх D7. Такой подход держит бизнес-логику внутри привычного ядра Битрикса, а GraphQL оставляет тонким транспортным слоем.
Запрос проходит по цепочке так: клиент отправляет POST на endpoint, слой безопасности проверяет глубину и стоимость, движок валидирует запрос по схеме, затем обходит дерево и для каждого поля вызывает резолвер, а тот берёт данные из источников (с кэшем и батчингом). Собранный JSON возвращается клиенту. Если ответ медленный — смотрим резолверы и источники; если запрос вообще отклонён — смотрим слой безопасности и схему.
Схема (SDL): типы, запросы, мутации
Схема — сердце GraphQL. Она описывается на языке SDL (Schema Definition Language) и служит единым контрактом: клиент видит все доступные типы и поля, а сервер по этой же схеме валидирует входящие запросы. Проектирование схемы — это, по сути, моделирование предметной области магазина в терминах, удобных фронтенду, а не таблиц базы.
Минимальный пример схемы каталога может выглядеть так:
type Product {
id: ID!
name: String!
slug: String!
price: Money!
inStock: Boolean!
section: Section
images: [Image!]!
props: [PropertyValue!]!
}
type Section {
id: ID!
name: String!
products(limit: Int = 20, offset: Int = 0): [Product!]!
}
type Query {
product(id: ID!): Product
catalog(sectionId: ID!, filter: ProductFilter): [Product!]!
}
type Mutation {
addToCart(productId: ID!, qty: Int = 1): Cart!
}
Обратите внимание на несколько принципов. Восклицательный знак означает non-null — поле гарантированно вернёт значение. Связи между сущностями описываются как вложенные типы (у товара есть section, у раздела — список товаров), и клиент может обходить их в глубину одним запросом. Аргументы полей (limit, filter) позволяют управлять выборкой прямо из запроса, что заменяет россыпь узких REST-методов.
Отдельного внимания заслуживает моделирование доменных типов вроде Money. Не отдавайте цену «сырым» числом с плавающей точкой — заведите тип с суммой, валютой и, при необходимости, форматированным представлением. Это избавляет фронтенд от логики форматирования и локализации и делает контракт устойчивее к изменениям. Тот же принцип касается остатков, скидок и торговых предложений: описывайте их как осмысленные доменные типы, а не как плоский слепок полей инфоблока.
Резолверы и доступ к данным через D7
Резолвер — это функция, которая знает, как получить значение конкретного поля. Движок GraphQL обходит дерево запроса и для каждого поля вызывает соответствующий резолвер, передавая ему родительский объект, аргументы и контекст запроса. Именно здесь GraphQL-слой встречается с Битриксом: внутри резолвера вы обращаетесь к D7 API.
Схематично резолвер товара по идентификатору на PHP выглядит так:
use Bitrix\Iblock\Elements\ElementCatalogTable;
$resolvers['Query']['product'] = function ($root, $args, $context) {
$id = (int)$args['id'];
$element = ElementCatalogTable::getByPrimary($id, [
'select' => ['ID', 'NAME', 'CODE'],
])->fetchObject();
if (!$element) {
return null;
}
return [
'id' => $element->getId(),
'name' => $element->getName(),
'slug' => $element->getCode(),
// цены и остатки резолвятся отдельными полями лениво
];
};
Ключевая идея — ленивость. Резолвер верхнего уровня не тянет сразу все цены, картинки и остатки. Он возвращает «заготовку» товара, а поля price, images, inStock резолвятся отдельными функциями и только если клиент их запросил. Если приложению нужны лишь название и цена, резолверы картинок и характеристик вообще не вызовутся, и лишних запросов к базе не будет.
При работе с D7 придерживайтесь нескольких правил, чтобы резолверы оставались быстрыми и предсказуемыми. Всегда указывайте явный select с нужными полями — не тяните всю модель. Выносите обращения к каталогу и ценам в отдельные источники данных, чтобы их можно было кэшировать и батчить. И держите бизнес-правила (доступность товара, расчёт цены для группы пользователя) внутри сервисов ядра, а не размазывайте по резолверам — резолвер должен оставаться тонкой обёрткой над доменной логикой.
Проблема N+1 и DataLoader
Это главная техническая ловушка любого GraphQL-сервера, и на каталоге Битрикса она проявляется особенно ярко. Представьте запрос: «дай 20 товаров раздела, и у каждого — его раздел и остаток». Наивная реализация сделает один запрос за списком товаров, а затем по одному запросу за разделом и остатком на каждый из 20 товаров. Итого 1 + 20 + 20 = 41 обращение к базе там, где могло быть три.
Отсюда и название — N+1: один запрос за списком плюс N запросов за деталями каждого элемента. С ростом выборки и вложенности число паразитных обращений растёт лавинообразно, и быстрый на демо-данных GraphQL превращается в тормоз на реальном каталоге.
Решение — паттерн DataLoader. Вместо того чтобы грузить связанную сущность немедленно, резолвер регистрирует в загрузчике нужный идентификатор и возвращает обещание. Загрузчик копит идентификаторы в пределах одного тика выполнения, а затем делает один пакетный запрос по всем сразу и раздаёт результаты обратно. 20 обращений за разделами превращаются в одно с условием ID IN (...).
// Псевдокод батч-загрузчика разделов
$sectionLoader = new DataLoader(function (array $ids) {
$rows = SectionTable::getList([
'filter' => ['@ID' => $ids],
'select' => ['ID', 'NAME'],
])->fetchAll();
// вернуть значения в том же порядке, что и $ids
return map_by_id($rows, $ids);
});
// В резолвере товара:
$resolvers['Product']['section'] = function ($product) use ($sectionLoader) {
return $sectionLoader->load($product['sectionId']);
};
Загрузчик создаётся на каждый запрос и живёт в контексте — так он попутно кэширует уже загруженные сущности в пределах одного обращения. Если один и тот же раздел встречается у пяти товаров, из базы он поднимется один раз. Именно связка «ленивые резолверы + DataLoader» отделяет production-ready GraphQL от учебного примера.
SELECT … IN.Кэширование ответов GraphQL
Кэш в GraphQL устроен иначе, чем в REST, и это одна из главных причин, почему переход требует инженерной аккуратности. В REST ответ привязан к URL, поэтому его легко кэшировать на уровне HTTP и CDN. В GraphQL все запросы идут POST-ом на один URL с разными телами, и кэшировать ответ целиком почти бессмысленно — двух одинаковых запросов подряд может и не быть.
Поэтому кэшируют не ответ, а данные под ним — на нескольких уровнях:
| Уровень кэша | Что кэширует | Когда сбрасывается |
|---|---|---|
| Кэш резолвера / источника | Сущность по ID (товар, раздел, цена) | По тегам инфоблока при изменении элемента |
| Кэш в пределах запроса (DataLoader) | Повторные обращения к одной сущности | Автоматически по завершении запроса |
| Тегированный кэш Битрикса | Выборки D7 с привязкой к тегам | Событиями ядра при правке данных |
| Персистентные запросы на GET | Целый ответ по хэшу операции | По TTL, отдаётся через CDN |
Рабочая связка для магазина на Битриксе выглядит так. Источники данных (товар, цена, остаток по ID) заворачиваются в тегированный кэш ядра: значение кладётся в кэш с тегом инфоблока, а при изменении элемента через админку или обмен с 1С тег сбрасывается автоматически, и кэш инвалидируется точечно. Поверх этого DataLoader снимает дубли в пределах одного запроса. А для публичных, часто повторяющихся выборок (главная витрина, популярный раздел) применяют персистентные запросы, которые можно отдавать через GET и кэшировать на CDN.
Отдельно предостережём от типичной ошибки — попытки закэшировать всё тело ответа по хэшу произвольного запроса. Из-за комбинаторики полей и аргументов процент попаданий будет низким, а инвалидация — кошмаром: непонятно, какой закэшированный ответ сбрасывать при изменении одного товара. Кэшируйте маленькие, адресуемые по ID куски данных с понятными тегами — это и эффективнее, и предсказуемее.
Безопасность: глубина, сложность, лимиты
Гибкость GraphQL — это и его уязвимость. Клиент сам конструирует запрос, а значит, может (случайно или намеренно) построить такой, который положит сервер. Классический пример — глубоко вложенный циклический запрос: товар → его раздел → товары раздела → их разделы → и так далее. Без ограничений один POST способен породить экспоненциальный обход и исчерпать ресурсы.
Базовый набор защит, который должен стоять на любом публичном GraphQL-endpoint:
- Ограничение глубины (max depth). Запрещаем запросы вложенностью больше разумного порога (например, 8–10 уровней). Отсекает циклические и «раскручивающиеся» запросы.
- Стоимость запроса (query complexity). Каждому полю назначается «вес», у запроса считается суммарная стоимость, и при превышении лимита он отклоняется до выполнения. Поля со списками получают вес с учётом множителя по количеству.
- Тайм-аут выполнения. Жёсткий предел времени на запрос, чтобы одна тяжёлая операция не держала процесс бесконечно.
- Обязательная пагинация. Списочные поля не отдают «всё сразу» — только страницами с лимитом сверху. Это и производительность, и защита.
- Rate limiting. Ограничение частоты запросов на клиента/токен, чтобы отсечь перебор и парсинг.
- Отключение интроспекции в проде. Для публичных API схему-самоописание часто закрывают, чтобы не облегчать атакующему разведку.
Расчёт стоимости — самый мощный из этих механизмов, потому что он учитывает не форму, а «тяжесть» запроса. Запрос из одного поля дёшев независимо от синтаксиса, а вот выборка тысячи товаров с вложенными свойствами и остатками получит высокую стоимость и будет отклонена до того, как коснётся базы. Пороги подбирают по реальной нагрузке: слишком строгий лимит ломает легитимные экраны, слишком мягкий — не защищает.
Авторизация на уровне полей и резолверов
Защита от перегрузки — только половина безопасности. Вторая половина — контроль доступа к данным. В магазине далеко не всё публично: цена может зависеть от группы пользователя (розница, опт, дилер), персональные данные заказа доступны только владельцу, а внутренние поля товара (себестоимость, поставщик) вообще не должны утекать наружу.
В GraphQL авторизацию удобно делать на нескольких уровнях. На уровне запроса решается, пущен ли клиент к endpoint вообще (по токену, сессии Битрикса, OAuth-приложению). На уровне резолвера — доступен ли конкретный объект этому пользователю (свой заказ против чужого). На уровне поля — можно ли отдать именно это поле (оптовая цена только авторизованному дилеру). Контекст запроса, куда кладётся текущий пользователь и его права, прокидывается во все резолверы и служит основой для этих проверок.
Практический принцип — не доверять клиенту в вопросах прав и всегда сверяться с ядром Битрикса. Группа пользователя, права на инфоблок, признак авторизации — всё это берётся из серверной сессии/токена, а не из параметров запроса. Цена для группы рассчитывается штатными механизмами каталога, а не «подставляется» на фронтенде. GraphQL здесь лишь транспорт: реальные правила доступа живут в ядре, и резолверы обязаны их уважать.
Отдельно стоит продумать сообщения об ошибках. GraphQL возвращает ошибки в структурированном виде, и заманчиво отдать клиенту подробности («у пользователя нет права X на объект Y»). Для публичного API это утечка: наружу отдавайте нейтральное «доступ запрещён», а детали пишите в лог на сервере. Тему защиты кастомного кода мы подробно разбираем в материалах рубрики Разработка и архитектура.
Персистентные запросы и APQ
Персистентные запросы (persisted queries) — приём, который решает сразу три задачи: безопасность, кэш и трафик. Идея проста: вместо того чтобы принимать любой запрос от клиента, сервер знает заранее одобренный список операций, и клиент присылает не текст запроса, а его короткий идентификатор — хэш.
Что это даёт на практике:
- Безопасность. Сервер выполняет только операции из белого списка. Произвольный вредоносный запрос просто не будет принят — его хэша нет в реестре.
- Кэш и CDN. У запроса появляется стабильный ключ (хэш), поэтому его можно отправлять через GET и кэшировать на CDN, как обычный REST-ресурс.
- Экономия трафика. По сети идёт короткий хэш вместо длинного тела запроса — заметно для мобильных клиентов на медленной сети.
Разновидность подхода — Automatic Persisted Queries (APQ). Клиент сначала пробует отправить только хэш; если сервер такой операции ещё не знает, он просит прислать полный текст один раз, регистрирует его и дальше принимает уже по хэшу. Так белый список наполняется автоматически из легитимного клиента, без ручного ведения реестра, но с сохранением преимуществ по кэшу и трафику.
Для публичного магазинного API персистентные запросы — практически обязательная практика в production. Они превращают «открытый для любых запросов» GraphQL в контролируемый набор операций, который вы реально тестировали и оптимизировали. Заодно это упрощает мониторинг: у каждой операции есть имя и хэш, по которым удобно собирать метрики и находить медленные запросы.
Реализация в 1С-Битрикс
Перейдём от концепций к конкретике: как собрать GraphQL-слой в проекте на Битриксе, не ломая существующий сайт. Ниже — проверенная последовательность шагов, которую мы используем на внедрениях.
- Заведите свой модуль или компонент под API. Не размазывайте код по
init.php— оформите GraphQL-слой как отдельный модуль (папкаlocal/modules/vendor.graphql). Так его удобно версионировать, включать и переносить между проектами. - Подключите библиотеку GraphQL. Через Composer добавьте
webonyx/graphql-phpв проект. Битрикс совместим с Composer, автозагрузку подключите в модуле. Это движок парсинга и валидации; данные вы пишете сами. - Опишите схему. Соберите типы каталога, заказов и пользователя в SDL или программно. Начните с узкого среза (товар, раздел, корзина) и расширяйте по мере надобности экранов.
- Напишите резолверы поверх D7. Для каждого поля — тонкий резолвер, обращающийся к
Bitrix\Iblock, модулюcatalogиsale. Бизнес-логику держите в сервисах ядра, резолвер оставляйте обёрткой. - Добавьте источники данных с DataLoader. Оберните загрузку товаров, разделов, цен и остатков в батч-загрузчики и тегированный кэш ядра. Это снимает N+1 и лишние обращения к базе.
- Настройте endpoint и контекст. Создайте публичную точку входа (например,
/api/graphql/), где инициализируется пролог Битрикса, определяется текущий пользователь и собирается контекст запроса с загрузчиками и правами. - Включите защиту. Навесьте правила глубины и стоимости запроса, тайм-аут, пагинацию и rate limiting. Для публичного API подготовьте персистентные запросы и отключите интроспекцию в проде.
- Свяжите инвалидацию с обменом. Убедитесь, что изменение товара в админке и обмен с 1С сбрасывают теги кэша резолверов — иначе на витрине повиснут старые цены и остатки.
Отдельно про пролог Битрикса на endpoint. GraphQL-обработчику нужны и авторизация, и доступ к модулям ядра, поэтому подключение prolog_before.php (или инициализация приложения D7) обязательно — так вы получаете текущего пользователя, его группы и права «бесплатно», штатными средствами. Не изобретайте собственную авторизацию поверх Битрикса: используйте его сессии, токены и OAuth-приложения, о которых речь в разборе REST API в Битриксе.
Если вы строите полноценную headless-витрину, где Битрикс остаётся бэкендом, а фронтенд отделён, GraphQL становится её центральным контрактом. Как устроен такой разрыв витрины и бэкенда, мы разбираем в статье про headless-коммерцию на Битриксе — GraphQL-слой из этой статьи там играет роль API-шлюза между слоями.
Запрос с фронтенда: Vue и React
Со стороны клиента GraphQL интегрируется через специализированные клиенты (Apollo, urql, Relay) или простыми fetch-запросами. Суть одинакова: фронтенд формирует запрос с нужными полями, отправляет POST на endpoint и получает JSON той же формы. Вот как выглядит запрос карточки товара:
query ProductCard($id: ID!) {
product(id: $id) {
name
price { amount currency }
inStock
images(limit: 1) { url }
}
}
Ответ придёт ровно по этой форме: объект product с четырьмя запрошенными полями. Никаких лишних свойств инфоблока, никакого второго запроса за ценами — всё в одном обращении. Разные экраны переиспользуют один тип Product, запрашивая под себя разный срез: списку каталога хватит имени и цены, а полной карточке нужны характеристики, галерея и остатки по складам.
Для команд, которые уже работают с Vue или React, GraphQL хорошо ложится на компонентный подход: каждый компонент декларирует свои требования к данным рядом с разметкой, а клиент собирает их в оптимальный запрос. Как выбрать и подключить современный фронтенд-стек к Битриксу, мы разбираем в отдельном материале про современный фронтенд на Vue и React поверх Битрикса — GraphQL там становится естественным способом связать SPA с данными магазина.
Практический совет по фронтенду: не запрашивайте «про запас». Соблазн взять сразу все поля товара «чтобы точно хватило» убивает главное преимущество GraphQL и возвращает вас к over-fetching REST. Запрашивайте ровно то, что рисует конкретный экран, — тогда и трафик, и нагрузка на резолверы останутся минимальными.
Мобильные приложения и GraphQL
Мобильное приложение магазина — сценарий, где GraphQL раскрывается сильнее всего. У мобильного клиента жёсткие ограничения, которых нет у десктопной витрины: нестабильная сеть, дорогой и медленный трафик, батарея, разные версии приложения у пользователей одновременно. GraphQL адресует почти каждое из этих ограничений.
- Меньше обращений на нестабильной сети. Один запрос вместо цепочки REST-вызовов снижает вероятность, что экран «недогрузится» из-за оборванного соединения на середине.
- Экономия трафика. Точный набор полей и персистентные запросы (короткий хэш вместо тела) заметно уменьшают объём данных по сети.
- Разные экраны — один контракт. Список, карточка, избранное используют один тип товара с разным срезом полей, без новых методов на бэкенде под каждый экран.
- Совместимость версий. Схема позволяет добавлять поля, не ломая старые версии приложения: они просто не запрашивают новое. Устаревшие поля помечаются как
@deprecated, а не удаляются резко.
Эволюция схемы — отдельная важная тема именно для мобильных, потому что у пользователей одновременно живут разные версии приложения, которые вы не можете обновить принудительно. Здесь работает правило аддитивных изменений: новые поля добавляем, старые не ломаем и не удаляем внезапно, а сначала помечаем устаревшими и даём время клиентам мигрировать. Строгая типизация схемы и здесь помогает: инструменты сразу показывают, какие клиенты какие поля используют.
С точки зрения бэкенда мобильный трафик — это ещё и повышенные требования к защите и кэшу, о которых мы говорили выше. Персистентные запросы, лимиты стоимости и тегированный кэш здесь не «желательно», а обязательно: приложение генерирует много однотипных запросов, и без кэша каждый из них будет бить по каталогу.
Мониторинг и метрики
GraphQL-слой нельзя запустить и забыть — за ним нужно наблюдать, причём в разрезе операций, а не только endpoint целиком. «API тормозит» — бесполезная формулировка, когда за одним URL скрываются десятки разных запросов. Вот метрики, на которые стоит смотреть.
| Метрика | Что показывает | Ориентир |
|---|---|---|
| Время выполнения по операции | Скорость каждого именованного запроса | Держать хвост p95 под контролем |
| Число запросов к базе на операцию | Есть ли N+1 и работает ли DataLoader | Единицы, а не десятки на запрос |
| Стоимость (complexity) запросов | Насколько тяжёлые запросы приходят | Ниже установленного лимита |
| Доля отклонённых запросов | Срабатывание защиты по глубине/стоимости | Всплеск — сигнал атаки или кривого клиента |
| Hit rate кэша резолверов | Эффективность кэширования данных | Высокий для товаров и разделов |
| Ошибки в поле errors | Частичные и полные сбои резолверов | Минимум, с алертами по всплескам |
Важная особенность GraphQL — частичные ответы. В отличие от REST, где запрос либо успешен, либо нет, GraphQL может вернуть часть данных и массив ошибок по отдельным полям. Это удобно для устойчивости (упал резолвер отзывов — карточка всё равно отрисуется), но требует внимания в мониторинге: успешный HTTP-статус 200 может скрывать ошибки внутри тела. Считайте ошибки по полю errors, а не только по кодам HTTP.
Число запросов к базе на операцию — самая ценная метрика для здоровья GraphQL. Если оно растёт с размером выборки линейно или хуже, значит где-то протёк N+1 и DataLoader не срабатывает. Держите эту метрику под наблюдением в связке с временем выполнения — вместе они быстро выводят на медленные и «прожорливые» резолверы.
Частые ошибки
- Пишут наивные резолверы без DataLoader — на каждом списке всплывает N+1, и GraphQL тормозит сильнее исходного REST.
- Открывают публичный endpoint без лимитов глубины и стоимости — один вложенный запрос кладёт сервер.
- Проектируют схему по структуре инфоблоков, а не по потребностям экранов — контракт получается неудобным, а менять его дорого.
- Пытаются кэшировать целые ответы по хэшу произвольного запроса — низкий hit rate и невозможная инвалидация.
- Не связывают кэш резолверов с тегами инфоблоков — после обмена с 1С на витрине висят старые цены и остатки.
- Отдают оптовые и внутренние поля без проверки прав — цена для дилеров и себестоимость утекают наружу.
- Возвращают клиенту подробные тексты ошибок авторизации — облегчают разведку атакующему.
- Тянут «все поля про запас» с фронтенда — возвращаются к over-fetching и сводят на нет главный плюс GraphQL.
- Резко удаляют или переименовывают поля схемы — ломают уже выпущенные мобильные приложения у пользователей.
Чек-лист внедрения
Сведём всё в практический список, по которому удобно принимать готовность GraphQL-слоя перед запуском.
- Оценён сценарий. Есть несколько клиентов (SPA, мобильное приложение) с разными требованиями к данным — GraphQL оправдан, а не взят «за моду».
- Схема спроектирована от экранов. Типы и поля названы осмысленно, доменные сущности (цена, остаток) выделены, контракт продуман на будущее.
- Резолверы тонкие. Логика в сервисах ядра, резолверы — обёртки над D7 с явным select и ленивой загрузкой полей.
- N+1 закрыт. Все списочные связи идут через DataLoader; число запросов к базе на операцию проверено и стабильно.
- Кэш связан с тегами. Источники данных кэшируются тегированным кэшем Битрикса, обмен с 1С инвалидирует нужные записи.
- Защита включена. Стоят лимиты глубины и стоимости, тайм-аут, пагинация, rate limiting; интроспекция в проде закрыта.
- Авторизация на месте. Права берутся из ядра, поля с ограниченным доступом проверяются, наружу уходят нейтральные ошибки.
- Публичные операции персистентны. Частые запросы оформлены как persisted queries, кэшируются и мониторятся по имени.
- Мониторинг настроен. Собираются время по операции, число запросов к базе, стоимость, ошибки в поле errors и hit rate кэша.
Вывод
GraphQL поверх 1С-Битрикс — это не миграция и не мода, а удобный API-фасад для случаев, когда у данных магазина появляется несколько разных потребителей: SPA-витрина, мобильное приложение, партнёрские интеграции. Он снимает боль over-fetching и under-fetching, даёт единый типизированный контракт и позволяет клиенту брать ровно нужное одним запросом. Но за гибкость приходится платить инженерной аккуратностью: без DataLoader вы получите N+1, без лимитов глубины и стоимости — уязвимость, без связки кэша с тегами инфоблоков — устаревшие данные на витрине. Сделайте эти вещи правильно — и GraphQL станет прочным мостом между быстрым фронтендом и надёжным ядром Битрикса.