GraphQL
GraphQL всплывает там, где REST начинает жать: клиенту нужно то три поля из тридцати, то данные из пяти эндпоинтов за один экран. Идея простая - пусть клиент в запросе сам опишет нужный граф данных, а сервер вернёт ровно его. Но за гибкость платят: N+1, дорогие запросы, другое кэширование.
Пройдём устройство (одна точка входа, схема, резолверы, три операции), главную ловушку N+1 и её лекарство DataLoader, а также то, как GraphQL отдаёт ошибки и когда честнее взять REST.
Схема, операции, резолверы
Обычно у GraphQL одна точка входа /graphql и строго типизированная схема. Клиент перечисляет нужные поля это лечит over-fetching (лишние поля) и under-fetching (нехватку, из-за которой в REST делают несколько запросов). Тип по умолчанию nullable; ! делает его non-null - добавить ! к существующему полю ломающее, убрать безопасно.
Три корневые операции: Query (чтение без побочных эффектов), Mutation (изменение; сложные аргументы оформляют input-типом), Subscription (поток серверных событий, обычно поверх WebSocket). За каждым полем стоит резолвер - функция, добывающая значение из БД или другого сервиса.
type Query {
order(id: ID!): Order
}
type Order { id: ID!, buyer: User! }N+1 и DataLoader, ошибки, выбор
Гибкость выборки рождает N+1: запросили 50 заказов и для каждого покупателя - наивный резолвер дёрнет 1+50 запросов к БД. Лечит DataLoader: он откладывает вызовы в пределах тика, собирает все id и грузит их одним batch-запросом, кешируя результат на время запроса. Списки страницируют курсорными connections - они устойчивее offset к вставкам.
Ошибки GraphQL-over-HTTP обычно едут в массиве errors при статусе 200: норма - частичный ответ, где часть графа в data, часть в errors. Свободу формы ограничивают (глубина, сложность, таймаут, persisted queries), а права проверяют на уровне полей. Для простого ресурсного CRUD с опорой на HTTP-кэш REST нередко проще.
Как отвечать: «Откуда в GraphQL берётся N+1 и чем его лечат?»
Форму ответа задаёт клиент, и за каждым полем стоит свой резолвер. Когда запросили список и для каждого элемента - связанную сущность, наивный резолвер связи дёргает по отдельному запросу на элемент: получается 1 запрос на список плюс N на связи. Лечу это DataLoader: он батчит id, собранные за тик, в один запрос к БД и кеширует ответ в пределах запроса. Так N+1 схлопывается в пару запросов.
Ответ показывает, что человек понимает механику резолверов и знает стандартное лекарство, а не просто хвалит гибкость GraphQL.
На чём валят
- −Наивные резолверы связей → N+1; лечит DataLoader (батчинг id + кэш в пределах запроса).
- −Ждать REST-коды: ошибка поля приходит в errors при HTTP 200, а data частичный.
- −Не лимитировать глубину/сложность запроса - один вложенный запрос кладёт сервис.
- −Проверять права только на входе /graphql вместо авторизации на уровне полей.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.
- Ключевое отличие GraphQL от REST по устройству эндпоинтов?A)GraphQL работает поверх WebSocket, а REST — исключительно поверх HTTPB)Одна точка входа и схема-типы вместо множества URL-ресурсовC)В GraphQL нет типов данных, клиент присылает произвольный JSON без схемыD)GraphQL полностью отменяет необходимость в базе данных, обращаясь сразу к файлам на диске
показать ответ и разбор
+B)Одна точка входа и схема-типы вместо множества URL-ресурсов// разбор: REST раскладывает данные по множеству URL-ресурсов с методами. GraphQL обычно даёт одну точку входа (/graphql) и типизированную схему: клиент шлёт запрос, описывающий нужный граф данных. Отсюда и гибкость выборки, и другой стиль версионирования — схему развивают, добавляя поля, а не плодя /v2.
- Что в схеме GraphQL делает резолвер (resolver) поля?A)Описывает тип поля в схеме, но сам никаких данных не достаётB)Проверяет синтаксис входящего запроса и отклоняет его при ошибке разбораC)Функция, добывающая значение поля из источника данныхD)Кэширует результат всего запроса целиком в памяти сервера между разными клиентами
показать ответ и разбор
+C)Функция, добывающая значение поля из источника данных// разбор: Резолвер — функция за полем схемы: она знает, откуда взять значение (БД, другой сервис, вычисление). Движок обходит запрос и вызывает резолверы по дереву полей. Именно поэтому наивные резолверы связей легко порождают N+1: для каждого элемента списка дёргается свой резолвер с отдельным запросом.
- Запрос тянет список из 50 заказов и для каждого — покупателя. Наивные резолверы дают 1+50 запросов к БД. Чем это лечат?A)Запретить вложенные поля в схеме, чтобы связи грузились отдельным ручным запросомB)Поднять таймаут БД, тогда 51 запрос успеет выполниться и проблема исчезнетC)Перевести резолверы на другой язык программирования ради ускорения каждого из запросовD)DataLoader: батчинг id за тик + кэш в пределах запроса
показать ответ и разбор
+D)DataLoader: батчинг id за тик + кэш в пределах запроса// разбор: Классический N+1: резолвер покупателя вызывается на каждый из 50 заказов отдельным SELECT. DataLoader откладывает вызовы в пределах тика, собирает все id и грузит их одним batch-запросом (IN (...)), кешируя результат на время запроса. Это стандартное лекарство N+1 в GraphQL-резолверах.
- Query, Mutation и Subscription в GraphQL — за что отвечают?A)Query — чтение, Mutation — изменение, Subscription — поток событийB)Это три уровня доступа к API: гость, пользователь и администратор системыC)Query для одного объекта, Mutation для списка, Subscription для агрегатов и статистикиD)Три обязательных этапа запроса, которые клиент проходит строго по порядку один за другим
показать ответ и разбор
+A)Query — чтение, Mutation — изменение, Subscription — поток событий// разбор: GraphQL различает три корневые операции: Query читает данные (без побочных эффектов), Mutation меняет состояние и возвращает результат, Subscription — долгоживущий поток серверных событий (обычно поверх WebSocket). Разделение по намерению помогает и кэшированию (Query кэшируем), и правам.
- GraphQL даёт клиенту свободу формы запроса. Какой риск это создаёт на сервере и чем управляют?A)Никакого: раз клиент сам выбрал поля, серверу остаётся лишь отдать их без нагрузкиB)Дорогие/глубокие запросы — ограничивают глубину, сложность, таймаутC)Только риск раскрытия схемы — его снимают, отключив интроспекцию на продеD)Сервер обязан хранить каждый уникальный запрос клиента в базе на случай его повторного вызова
показать ответ и разбор
+B)Дорогие/глубокие запросы — ограничивают глубину, сложность, таймаут// разбор: Клиент может собрать глубоко вложенный или широкий запрос, дорогой по БД и CPU. Поэтому вводят ограничения: максимальная глубина, оценка сложности (стоимость полей), пагинация обязательных списков, таймауты, а иногда — разрешённый список запросов (persisted queries). Иначе один запрос кладёт сервис.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.