Разработка GraphQL API для 1С-Битрикс
GraphQL решает конкретную проблему REST API: клиент получает либо слишком мало данных (underfetching — нужно несколько запросов), либо слишком много (overfetching — поля, которые никому не нужны). В GraphQL клиент сам описывает, какие поля ему нужны, и получает ровно то, что запросил. Для 1С-Битрикс это особенно актуально при работе с вложенными структурами: товар → торговые предложения → цены → остатки — всё в одном запросе.
Почему GraphQL, а не REST
REST-эндпоинт /api/products/123 возвращает фиксированный набор полей. Мобильному приложению нужны имя и цена — оно получает 40 полей. Другому клиенту нужны остатки по складам — он делает второй запрос. GraphQL позволяет каждому клиенту описать свои потребности:
# Мобильное приложение
query {
product(id: 123) {
name
price { value currency }
images { url }
}
}
# Складской модуль
query {
product(id: 123) {
name
sku { id stock { warehouse quantity } }
}
}
Один эндпоинт, один запрос — разные данные для разных клиентов.
Интеграция GraphQL с Битрикс
Битрикс не поддерживает GraphQL из коробки. Реализация строится поверх стандартного PHP Битрикс через библиотеку webonyx/graphql-php — это де-факто стандарт для PHP.
Точка входа — один контроллер на URL /api/graphql, который принимает POST-запросы с JSON-телом ({ "query": "...", "variables": {...} }).
// /local/php_interface/api/graphql.php
use GraphQL\GraphQL;
use GraphQL\Type\Schema;
$rawInput = file_get_contents('php://input');
$input = json_decode($rawInput, true);
$schema = new Schema([
'query' => QueryType::build(),
'mutation' => MutationType::build(),
]);
$result = GraphQL::executeQuery($schema, $input['query'], null, null, $input['variables'] ?? null);
header('Content-Type: application/json');
echo json_encode($result->toArray());
Схема типов для Битрикс
Каждый тип GraphQL соответствует сущности Битрикс. Пример для каталога:
// ProductType
ObjectType(['name' => 'Product', 'fields' => fn() => [
'id' => ['type' => Type::int()],
'name' => ['type' => Type::string()],
'code' => ['type' => Type::string()],
'price' => [
'type' => PriceType::get(),
'resolve' => fn($product) => PriceResolver::resolve($product['ID']),
],
'sku' => [
'type' => Type::listOf(SkuType::get()),
'resolve' => fn($product) => SkuResolver::resolve($product['ID']),
],
'sections' => [
'type' => Type::listOf(SectionType::get()),
'resolve' => fn($product) => SectionResolver::resolve($product['IBLOCK_SECTION_ID']),
],
]]);
Резолверы (resolvers) — функции, которые получают данные для каждого поля. Резолвер для price обращается к b_catalog_price, для sku — к дочернему инфоблоку SKU, для sections — к b_iblock_section.
Проблема N+1 и DataLoader
Главная проблема GraphQL — N+1 запросов. Клиент запросил список из 20 товаров, для каждого нужны цены — 20 отдельных запросов к b_catalog_price. Решение: DataLoader (паттерн batching).
DataLoader накапливает запросы в рамках одного GraphQL-выполнения и делает один батч-запрос:
class PriceDataLoader
{
private array $buffer = [];
public function load(int $productId): Promise
{
$this->buffer[] = $productId;
return new Promise(fn($resolve) => $resolve($productId));
}
public function dispatch(): void
{
// Один запрос для всех накопленных ID
$prices = \Bitrix\Catalog\PriceTable::getList([
'filter' => ['PRODUCT_ID' => $this->buffer],
])->fetchAll();
// Распределяем результаты
}
}
Вместо 20 запросов — 1. Для вложенных данных (товары → SKU → остатки) экономия кратная.
Мутации: создание и обновление данных
Мутации в GraphQL — аналог POST/PUT/DELETE в REST:
mutation {
createOrder(input: {
productId: 123,
quantity: 2,
deliveryAddress: "Москва, ул. Ленина, 1"
}) {
orderId
status
totalAmount
}
}
Резолвер мутации вызывает \Bitrix\Sale\Order::create() с нужными параметрами — стандартное D7 API модуля sale.
Авторизация и разграничение доступа
Авторизация реализуется на двух уровнях:
Уровень запроса. Middleware проверяет JWT или сессию Битрикс перед выполнением GraphQL-запроса.
Уровень поля. Конкретное поле доступно только авторизованным пользователям. Например, поле costPrice (себестоимость) видит только пользователь с ролью «Администратор». Реализуется в резолвере:
'costPrice' => [
'type' => Type::float(),
'resolve' => function($product, $args, Context $context) {
if (!$context->user->hasRole('admin')) {
throw new \Exception('Нет доступа');
}
return $product['COST_PRICE'];
},
],
Кеширование GraphQL
GraphQL сложнее кешировать, чем REST: запросы уникальны по набору полей и переменных. Подходы:
- Кеш на уровне резолвера (наиболее распространённый): резолвер кеширует результат конкретного DataLoader-батча в Redis/Memcache. TTL зависит от частоты обновления данных.
- Persisted Queries: клиент отправляет хеш заранее зарегистрированного запроса вместо полного текста. Это позволяет кешировать на уровне HTTP (CDN кеширует GET-запросы с хешем).
-
Тегированный кеш Битрикс: регистрируем теги при чтении данных (
iblock_id_1), инвалидируем при изменении.
Подписки (Subscriptions)
GraphQL поддерживает подписки — реalttime обновления через WebSocket. При изменении заказа все подписчики получают уведомление. Для Битрикс реализуется через отдельный WebSocket-сервер (Ratchet/Swoole) + Redis pub/sub. При изменении сущности в Битрикс (через обработчик события) публикуем в Redis-канал, WebSocket-сервер доставляет всем подписчикам.
Introspection и документация
GraphQL предоставляет introspection — клиент может запросить полную схему API. На её основе GraphiQL (браузерный IDE) позволяет изучать API интерактивно. Для разработки это незаменимо: типы, описания, автодополнение запросов.
Этапы разработки
| Этап | Содержание | Срок |
|---|---|---|
| Проектирование схемы | Типы, запросы, мутации, связи | 1 неделя |
| Базовая инфраструктура | GraphQL endpoint, авторизация | 3–5 дней |
| Реализация типов и резолверов | Каталог, заказы, пользователи | 2–4 недели |
| DataLoader (N+1) | Batching для вложенных данных | 1 неделя |
| Кеширование | Redis DataLoader cache | 1 неделя |
| Авторизация полей | Разграничение доступа | 3–5 дней |
| Тестирование | Unit-тесты резолверов, интеграционные тесты | 1 неделя |
GraphQL на Битрикс — зрелое решение для проектов с несколькими клиентами и сложными вложенными данными. Для простого сайта с одним фронтендом — REST достаточно.







