сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · FastAPI, Django и API

GraphQL

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, остальные разбираются в тренажёре.

  1. #graphql_api1 / 5
    Ключевое отличие GraphQL от REST по устройству эндпоинтов?
    A)GraphQL работает поверх WebSocket, а REST — исключительно поверх HTTP
    B)Одна точка входа и схема-типы вместо множества URL-ресурсов
    C)В GraphQL нет типов данных, клиент присылает произвольный JSON без схемы
    D)GraphQL полностью отменяет необходимость в базе данных, обращаясь сразу к файлам на диске
    показать ответ и разбор
    +B)Одна точка входа и схема-типы вместо множества URL-ресурсов

    // разбор: REST раскладывает данные по множеству URL-ресурсов с методами. GraphQL обычно даёт одну точку входа (/graphql) и типизированную схему: клиент шлёт запрос, описывающий нужный граф данных. Отсюда и гибкость выборки, и другой стиль версионирования — схему развивают, добавляя поля, а не плодя /v2.

  2. #graphql_api2 / 5
    Что в схеме GraphQL делает резолвер (resolver) поля?
    A)Описывает тип поля в схеме, но сам никаких данных не достаёт
    B)Проверяет синтаксис входящего запроса и отклоняет его при ошибке разбора
    C)Функция, добывающая значение поля из источника данных
    D)Кэширует результат всего запроса целиком в памяти сервера между разными клиентами
    показать ответ и разбор
    +C)Функция, добывающая значение поля из источника данных

    // разбор: Резолвер — функция за полем схемы: она знает, откуда взять значение (БД, другой сервис, вычисление). Движок обходит запрос и вызывает резолверы по дереву полей. Именно поэтому наивные резолверы связей легко порождают N+1: для каждого элемента списка дёргается свой резолвер с отдельным запросом.

  3. #graphql_api3 / 5
    Запрос тянет список из 50 заказов и для каждого — покупателя. Наивные резолверы дают 1+50 запросов к БД. Чем это лечат?
    A)Запретить вложенные поля в схеме, чтобы связи грузились отдельным ручным запросом
    B)Поднять таймаут БД, тогда 51 запрос успеет выполниться и проблема исчезнет
    C)Перевести резолверы на другой язык программирования ради ускорения каждого из запросов
    D)DataLoader: батчинг id за тик + кэш в пределах запроса
    показать ответ и разбор
    +D)DataLoader: батчинг id за тик + кэш в пределах запроса

    // разбор: Классический N+1: резолвер покупателя вызывается на каждый из 50 заказов отдельным SELECT. DataLoader откладывает вызовы в пределах тика, собирает все id и грузит их одним batch-запросом (IN (...)), кешируя результат на время запроса. Это стандартное лекарство N+1 в GraphQL-резолверах.

  4. #graphql_api4 / 5
    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 кэшируем), и правам.

  5. #graphql_api5 / 5
    GraphQL даёт клиенту свободу формы запроса. Какой риск это создаёт на сервере и чем управляют?
    A)Никакого: раз клиент сам выбрал поля, серверу остаётся лишь отдать их без нагрузки
    B)Дорогие/глубокие запросы — ограничивают глубину, сложность, таймаут
    C)Только риск раскрытия схемы — его снимают, отключив интроспекцию на проде
    D)Сервер обязан хранить каждый уникальный запрос клиента в базе на случай его повторного вызова
    показать ответ и разбор
    +B)Дорогие/глубокие запросы — ограничивают глубину, сложность, таймаут

    // разбор: Клиент может собрать глубоко вложенный или широкий запрос, дорогой по БД и CPU. Поэтому вводят ограничения: максимальная глубина, оценка сложности (стоимость полей), пагинация обязательных списков, таймауты, а иногда — разрешённый список запросов (persisted queries). Иначе один запрос кладёт сервис.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.