Урок 07.04: GraphQL — альтернативный подход к API
Цель урока
Разобраться, как устроен GraphQL — альтернатива REST для проектирования API: язык описания схемы (SDL), запросы (Query), мутации (Mutation), подписки (Subscription), а также типичные проблемы (over-/under-fetching, N+1) и подходы к версионированию, ошибкам и ограничению нагрузки. Особый фокус: сравнение с REST/SOAP/gRPC и вопросы, которые чаще всего задают системным аналитикам на собеседованиях.
Ключевые понятия
| Термин | Определение |
|---|---|
| GraphQL | Язык запросов к API и runtime для его исполнения: клиент описывает, какие данные ему нужны, сервер возвращает ровно их |
| Schema (Схема) | Контракт API — описание всех типов, запросов, мутаций и подписок на языке SDL |
| SDL (Schema Definition Language) | Синтаксис описания GraphQL-схемы (type, enum, input, interface, union) |
| Query | Корневой тип для операций чтения данных |
| Mutation | Корневой тип для операций изменения данных (создание/обновление/удаление) |
| Subscription | Корневой тип для подписки на события в реальном времени (через WebSocket) |
| Resolver | Функция на сервере, которая возвращает значение для конкретного поля схемы |
| Over-fetching | Клиент получает больше полей, чем ему нужно |
| Under-fetching | Клиенту не хватает данных за один запрос, нужно делать дополнительные запросы |
| N+1 problem | Проблема производительности: 1 запрос к списку + N запросов к связанным сущностям по отдельности |
| Introspection | Способность GraphQL-сервера описать свою же схему по запросу — основа для автогенерации документации и автокомплита |
1. Зачем появился GraphQL: проблема REST
1.1. Аналогия: меню «комплексный обед» vs «конструктор блюда»
REST API — это меню из готовых блюд («комплексных обедов»): GET /tasks/42 всегда возвращает фиксированный набор полей, который определил разработчик сервера. Если вам нужно только название блюда, а салат и суп вам не нужны — придётся либо съесть всё («over-fetching»), либо доплачивать и идти за салатом в другое заведение («under-fetching» — отдельный запрос).
GraphQL — это «конструктор блюда»: клиент сам перечисляет, что ему нужно из доступного набора, и получает ровно это, за один «заказ» (один HTTP-запрос).
1.2. Over-fetching и Under-fetching на примере Task Manager
Продолжим пример Task Manager из предыдущих уроков. REST-эндпоинт возвращает полный объект задачи:
GET /api/v1/tasks/42
{
"id": 42,
"title": "Подготовить отчёт",
"description": "Очень длинный текст описания на 2000 символов...",
"status": "in_progress",
"priority": "high",
"created_at": "2026-06-01T10:00:00Z",
"updated_at": "2026-06-10T14:30:00Z",
"assignee_id": 7,
"project_id": 3,
"tags": ["backend", "Q2"],
"attachments": [ ... ]
}
Сценарий: мобильное приложение показывает список задач, и на экране нужны только title, status и имя исполнителя (а не assignee_id).
- Over-fetching: REST вернёт все 10+ полей, включая
descriptionна 2000 символов — лишний трафик, особенно заметно на мобильной сети. - Under-fetching: чтобы получить имя исполнителя, нужен второй запрос
GET /users/7— для списка из 50 задач это потенциально 50 дополнительных запросов (см. N+1, раздел 6.2).
GraphQL-запрос для того же экрана — один запрос, ровно нужные поля:
query {
tasks(status: IN_PROGRESS) {
title
status
assignee {
name
}
}
}
{
"data": {
"tasks": [
{ "title": "Подготовить отчёт", "status": "IN_PROGRESS", "assignee": { "name": "Иван Петров" } }
]
}
}
Для аналитика: если в требованиях написано «мобильное приложение должно работать с минимальным трафиком и поддерживать гибкие, часто меняющиеся экраны без переписывания backend» — это аргумент в пользу GraphQL. Если «контракт должен быть стабильным, явным и легко кэшируемым на уровне HTTP» — это аргумент в пользу REST (подробнее — раздел 10).
2. Схема (SDL) — контракт GraphQL API
2.1. Типы данных
| Тип | Назначение | Пример |
|---|---|---|
| Scalar (встроенные) | Int, Float, String, Boolean, ID |
title: String |
| Object type | Составной тип с полями | type Task { ... } |
| Enum | Перечисление допустимых значений | enum TaskStatus { TODO IN_PROGRESS DONE } |
| Input type | Тип для входных параметров мутаций (нельзя смешивать с Object type) | input CreateTaskInput { ... } |
| Interface / Union | Полиморфизм — аналог oneOf/allOf из OpenAPI (см. Урок 07.02) |
union SearchResult = Task | User |
Восклицательный знак ! означает non-nullable — поле не может быть null. title: String! — title всегда есть; description: String — может быть null.
2.2. Пример схемы Task Manager
type Task {
id: ID!
title: String!
description: String
status: TaskStatus!
priority: Priority!
assignee: User
comments: [Comment!]!
createdAt: String!
}
enum TaskStatus {
TODO
IN_PROGRESS
DONE
}
enum Priority {
LOW
MEDIUM
HIGH
}
type User {
id: ID!
name: String!
email: String!
}
type Comment {
id: ID!
text: String!
author: User!
createdAt: String!
}
input CreateTaskInput {
title: String!
description: String
priority: Priority!
assigneeId: ID
}
type Query {
task(id: ID!): Task
tasks(status: TaskStatus, assigneeId: ID, limit: Int = 20, offset: Int = 0): [Task!]!
}
type Mutation {
createTask(input: CreateTaskInput!): Task!
updateTaskStatus(id: ID!, status: TaskStatus!): Task!
addComment(taskId: ID!, text: String!): Comment!
}
type Subscription {
taskUpdated(id: ID!): Task!
}
Это та же модель Task/Comment/User, которая в REST-варианте описывалась как набор эндпоинтов /tasks, /tasks/{id}/comments, /users/{id} (см. задание 2 Урока 07.01) — GraphQL-схема — это один файл, описывающий все возможности API.
2.3. Introspection — самодокументируемость
GraphQL-сервер умеет отвечать на специальный запрос { __schema { types { name fields { name } } } } — описать сам себя. На этом строятся инструменты вроде GraphiQL и Apollo Studio: автокомплит, автогенерация документации и валидация запросов «из коробки», без отдельного шага «выгрузить и опубликовать спецификацию», как с OpenAPI.
3. Запросы (Query)
3.1. Базовый запрос
query {
task(id: "42") {
title
status
assignee {
name
email
}
}
}
Структура запроса повторяет форму ответа — это одна из причин, почему GraphQL легко читать.
3.2. Переменные (Variables)
Жёстко закодированные значения (id: "42") — плохая практика. Переменные передаются отдельно от текста запроса (аналог параметризованных SQL-запросов — защита от инъекций «из коробки»):
query GetTask($taskId: ID!) {
task(id: $taskId) {
title
status
}
}
// Variables
{ "taskId": "42" }
3.3. Алиасы и фрагменты
Алиас — переименование поля в ответе (полезно при запросе одного и того же поля с разными аргументами):
query {
highPriority: tasks(status: TODO, priority: HIGH) { title }
lowPriority: tasks(status: TODO, priority: LOW) { title }
}
Фрагмент — повторно используемый набор полей (аналог переиспользуемой схемы $ref в OpenAPI):
fragment TaskBrief on Task {
id
title
status
}
query {
tasks(status: IN_PROGRESS) { ...TaskBrief }
}
4. Мутации (Mutation)
Создание, обновление и удаление данных — через корневой тип Mutation. По конвенции мутация возвращает изменённый объект, чтобы клиент сразу обновил локальный кэш без дополнительного запроса:
mutation CreateTask($input: CreateTaskInput!) {
createTask(input: $input) {
id
title
status
createdAt
}
}
// Variables
{
"input": { "title": "Подготовить отчёт", "priority": "HIGH", "assigneeId": "7" }
}
Для аналитика: в отличие от REST, где POST /tasks (создание) и PATCH /tasks/{id} (изменение статуса) — это два разных эндпоинта с разной семантикой HTTP-методов (см. Урок 07.01), в GraphQL это две разные мутации (createTask, updateTaskStatus) в одном Mutation-типе — вся семантика именования и побочных эффектов лежит на названии мутации, а не на HTTP-методе (физически почти все GraphQL-запросы — это POST /graphql).
5. Подписки (Subscription) — реальное время
subscription {
taskUpdated(id: "42") {
id
status
updatedAt
}
}
Subscription держит открытое WebSocket-соединение — клиент получает событие сразу, когда сервер публикует его (через pubsub.publish(...) в резолвере).
Сравнение с другими подходами к «событиям», разобранными в модуле:
| GraphQL Subscription | Webhook (Урок 07.07) | Брокер сообщений (Урок 07.06) | |
|---|---|---|---|
| Кто инициирует | Клиент открывает WS-соединение | Источник делает HTTP-запрос на URL клиента | Producer пишет в топик/очередь, consumer читает |
| Нужен публичный endpoint у клиента? | Нет | Да | Нет |
| Типичный потребитель | Фронтенд (браузер/мобильное приложение) | Backend-система партнёра | Внутренний микросервис |
| Гарантии доставки | Нет (если клиент offline — событие потеряно) | At-least-once (с retry у источника) | Настраиваемые (at-least-once, exactly-once с Kafka) |
Для аналитика: Subscription — это «GraphQL-вариант» realtime для фронтенда (открытая вкладка браузера). Для интеграции между серверными системами, где нужна гарантия доставки и работа в фоне, — это webhook или брокер, а не GraphQL Subscription.
6. Resolvers и проблема N+1
6.1. Как сервер выполняет запрос
Каждое поле в схеме обслуживается функцией-резолвером. Для запроса:
query {
tasks(status: IN_PROGRESS) {
title
assignee { name }
}
}
Выполнение происходит послойно:
- Резолвер
Query.tasks— один запрос к БД: «дать все задачи со статусом IN_PROGRESS» → получено 50 задач - Для каждой из 50 задач вызывается резолвер
Task.assignee
6.2. N+1 problem
Если резолвер Task.assignee наивно реализован как «сходить в БД за пользователем по assigneeId» — для 50 задач это 50 отдельных запросов к БД плюс 1 исходный запрос за списком задач = N+1 запросов.
Запрос 1: SELECT * FROM tasks WHERE status = 'in_progress' → 50 строк
Запрос 2: SELECT * FROM users WHERE id = 7 (для задачи #1)
Запрос 3: SELECT * FROM users WHERE id = 7 (для задачи #2, тот же assignee!)
Запрос 4: SELECT * FROM users WHERE id = 12 (для задачи #3)
...
Запрос 51: SELECT * FROM users WHERE id = ... (для задачи #50)
Это классическая проблема производительности GraphQL — и один из самых частых вопросов на собеседовании.
6.3. Решение: DataLoader (batching + caching)
Вместо немедленного запроса к БД резолвер откладывает запрос и собирает все assigneeId за один «тик» event loop, затем делает один батч-запрос:
Вместо: SELECT * FROM users WHERE id = 7
SELECT * FROM users WHERE id = 7
SELECT * FROM users WHERE id = 12
... (50 запросов)
DataLoader делает:
SELECT * FROM users WHERE id IN (7, 12, 15, ...) -- 1 запрос
+ кэширует результат на время одного GraphQL-запроса
+ раздаёт каждому резолверу его пользователя из батча
Для аналитика: если в нефункциональных требованиях к GraphQL API не упомянут DataLoader (или эквивалентный механизм батчинга) для всех «один-к-многим» связей — это риск деградации производительности при росте объёма данных, который проявится не на тестах (где данных мало), а в продакшене.
7. Обработка ошибок
В отличие от REST, где ошибка = HTTP-статус 4xx/5xx (см. Урок 07.01), GraphQL почти всегда возвращает 200 OK, даже если часть запроса завершилась с ошибкой:
{
"data": {
"task": {
"title": "Подготовить отчёт",
"assignee": null
}
},
"errors": [
{
"message": "User with id 7 not found",
"path": ["task", "assignee"],
"extensions": { "code": "NOT_FOUND" }
}
]
}
Это называется partial response — часть данных вернулась успешно (title), часть — нет (assignee: null + запись в errors).
Для аналитика: в требованиях к GraphQL API важно зафиксировать:
- Используются ли коды ошибок в
extensions.code(например,UNAUTHENTICATED,NOT_FOUND,VALIDATION_ERROR) — без этого клиент может различать ошибки только по текстуmessage, что хрупко - В каких случаях сервер всё же вернёт HTTP-статус не 200 (обычно — только при синтаксической ошибке самого запроса или при сбое транспорта, не бизнес-логики)
8. Версионирование: эволюция схемы вместо /v1 → /v2
В REST новая несовместимая версия API — это /api/v2/... (см. Урок 07.01, раздел 10). В GraphQL принятый подход — одна постоянно эволюционирующая схема:
- Добавление поля — безопасно: старые клиенты, которые его не запрашивают, не замечают изменения
- Удаление поля — небезопасно, поэтому сначала помечается директивой:
type Task { assigneeId: ID @deprecated(reason: "Use 'assignee { id }' instead") assignee: User } - Поле остаётся доступным (но помечено как deprecated в introspection — IDE подсвечивают предупреждением), пока аналитика по логам не покажет, что старые клиенты перестали его запрашивать
Для аналитика: «версионирование» в GraphQL — это процесс управления жизненным циклом полей схемы (deprecation policy), а не выпуск параллельных версий эндпоинта. В ТЗ стоит фиксировать: как долго поле остаётся доступным после @deprecated, кто отслеживает использование устаревших полей.
9. Безопасность и ограничение нагрузки
9.1. Проблема: один запрос — произвольная вложенность
Клиент может отправить один запрос произвольной глубины:
query Evil {
task(id: "42") {
comments {
author {
tasks {
comments {
author {
tasks { comments { author { tasks { id } } } }
}
}
}
}
}
}
}
Формально это один HTTP-запрос — Rate Limiting «N запросов в минуту» из Урока 07.01, раздел 12 не защищает от такого запроса: один такой запрос может стоить как тысячи обычных.
9.2. Защитные механизмы
| Механизм | Что делает |
|---|---|
| Depth limiting | Запрос с вложенностью больше N (например, 7) отклоняется до выполнения |
| Query complexity / cost analysis | Каждому полю присваивается «стоимость» (например, поле со списком — дороже скалярного); запрос со стоимостью выше лимита отклоняется |
| Лимит по стоимости вместо count | Rate Limiting в GraphQL обычно считается в очках сложности за период, а не в «запросах в минуту» |
| Отключение Introspection в проде | Часто отключают __schema в production, чтобы не раскрывать структуру API публично |
Для аналитика: если REST-овский пункт «100 запросов в минуту» (раздел 12 Урока 07.01) механически перенести в ТЗ на GraphQL API — это не сработает. Для GraphQL в требованиях нужно указать максимальную глубину запроса и/или лимит сложности (query cost), а не только количество HTTP-запросов.
10. GraphQL vs REST vs SOAP vs gRPC
| Критерий | REST | GraphQL | SOAP | gRPC |
|---|---|---|---|---|
| Формат данных | JSON (обычно) | JSON | XML | Protocol Buffers (binary) |
| Контракт | OpenAPI (опционально) | Схема (обязательна, встроена в протокол) | WSDL (обязателен) | .proto файл |
| Гибкость запроса полей | Нет (фиксированный ответ на эндпоинт) | Да — клиент выбирает поля | Нет | Нет (фиксированные сообщения) |
| HTTP-методы/статусы | Используются по смыслу (GET/POST/PUT, 200/404/500) | Почти всё — POST /graphql, статус почти всегда 200 |
POST, статус почти всегда 200, ошибка — в SOAP Fault |
Использует HTTP/2 streams, свои статус-коды |
| Кэширование на уровне HTTP | Простое (по URL, через CDN/браузер) | Сложное (один и тот же URL /graphql для всех запросов) |
Сложное | Сложное |
| Загрузка файлов | Нативно (multipart/form-data) |
Не из коробки, нужны расширения | Через MTOM | Через streaming |
| Типичный сценарий | Публичные API, CRUD-сервисы | Фронтенд с разнообразными экранами, агрегация данных из нескольких источников | Enterprise/банки/1С (см. Урок 07.03) | Внутренние высокопроизводительные межсервисные вызовы |
Для аналитика: GraphQL особенно часто выбирают, когда один backend обслуживает несколько разных фронтендов (веб, мобильное приложение, партнёрский виджет) с разными требованиями к данным — паттерн BFF (Backend for Frontend, см. Урок 07.05) часто реализуется именно через GraphQL-гейтвей перед набором REST/gRPC-сервисов.
11. Инструменты
| Инструмент | Назначение |
|---|---|
| GraphiQL / GraphQL Playground | Встроенная IDE для отправки запросов с автокомплитом на основе introspection — аналог Swagger UI для REST |
| Apollo Studio | Платформа для управления GraphQL-схемой, мониторинга использования полей (включая @deprecated), composition нескольких схем (Federation) |
| Postman | Поддерживает GraphQL-запросы как отдельный тип (автоматически подгружает схему через introspection) — см. Урок 07.08 |
| Insomnia | Альтернатива Postman с сильной поддержкой GraphQL |
12. GraphQL: частые вопросы на собеседовании системного аналитика
Короткие ответы на вопросы, которые регулярно встречаются на собеседованиях — для быстрого повторения перед интервью.
1. Чем GraphQL принципиально отличается от REST?
REST — это набор фиксированных эндпоинтов с заранее заданной формой ответа. GraphQL — один эндпоинт (/graphql), где клиент сам описывает запросом, какие поля и связи ему нужны.
2. Что такое over-fetching и under-fetching? Over-fetching — сервер возвращает больше данных, чем нужно клиенту. Under-fetching — клиенту не хватает данных за один запрос, нужны дополнительные запросы. GraphQL решает обе проблемы за счёт того, что форма ответа = форма запроса.
3. Какой HTTP статус-код возвращает GraphQL при ошибке в запросе данных?
Обычно 200 OK, даже при ошибке — ошибка передаётся в массиве errors рядом с (частичными) data. Не-200 — только при сбое транспорта или синтаксической ошибке самого запроса.
4. Что такое resolver? Функция на сервере, которая возвращает значение конкретного поля схемы. Каждое поле в дереве запроса разрешается своим резолвером.
5. В чём суть проблемы N+1 и как её решают?
Наивная реализация резолвера для связанных сущностей делает по одному запросу к БД на каждый элемент списка (N запросов) плюс 1 исходный — итого N+1. Решается батчингом запросов (паттерн DataLoader): все ID собираются и запрашиваются одним IN (...)-запросом.
6. Как версионируется GraphQL API?
Отдельных версий (/v1, /v2) обычно нет — схема эволюционирует: новые поля добавляются, старые помечаются @deprecated и удаляются после того, как клиенты перестали их использовать.
7. Что такое mutation и чем она отличается от query?
Query — операции чтения (без побочных эффектов), Mutation — операции изменения данных (создание/обновление/удаление). По конвенции мутация возвращает изменённый объект.
8. Как в GraphQL реализуется realtime?
Через Subscription — клиент открывает WebSocket-соединение и получает события по подписке. Для серверных интеграций (без постоянного соединения) для realtime чаще используют webhooks или брокеры сообщений.
9. Какие риски безопасности специфичны для GraphQL? Произвольно глубокие/сложные запросы за один HTTP-вызов («один запрос — как тысяча обычных»). Защита — depth limiting и query complexity analysis, а не обычный rate limiting по количеству запросов.
10. Когда GraphQL — плохой выбор? Когда важны простое HTTP-кэширование, простые публичные API с предсказуемым набором эндпоинтов, передача файлов, или когда команда/инфраструктура не готовы поддерживать дополнительный слой (резолверы, DataLoader, мониторинг сложности запросов).
Вопросы для самопроверки
Базовый уровень
- Что такое схема (SDL) в GraphQL и чем она отличается от OpenAPI-спецификации по роли в проекте?
- Приведите пример over-fetching и under-fetching на примере любого REST API, который вы анализировали.
- Чем
Queryотличается отMutation? Может лиQueryизменять данные? - Что означает
!после типа поля в SDL (например,title: String!)? - Какой HTTP-метод и URL обычно используются для GraphQL-запросов?
Продвинутый уровень
- N+1: Дан запрос
tasks { title assignee { name } }, возвращающий 200 задач от 15 разных исполнителей. Сколько запросов к БД сделает наивная реализация без DataLoader? Сколько — с DataLoader? - Partial response: GraphQL вернул
200 OKсdata.task.assignee = nullи записью вerrorsс кодомNOT_FOUND. Что это значит для клиента, и как клиентскому приложению следует отрисовать такой ответ в UI? - Версионирование: Поле
Task.assigneeIdпомечено@deprecated(reason: "use assignee.id"). По логам видно, что 3 старых мобильных клиента продолжают его запрашивать. Можно ли удалить поле прямо сейчас? Что нужно сделать аналитику перед удалением? - Безопасность: Почему классический Rate Limiting «100 запросов в минуту» (раздел 12 Урока 07.01) недостаточен для защиты GraphQL API? Какие два механизма нужно добавить?
- Выбор технологии: Команда строит единый backend для веб-приложения, мобильного приложения и партнёрского виджета — у каждого свой набор нужных полей и они часто меняются. Внутри backend — 4 микросервиса на gRPC. Предложите архитектуру с использованием GraphQL и обоснуйте роль каждой технологии.
Практическое задание
Задание 1. Перевод REST в GraphQL-схему (2 балла)
В Уроке 07.01 (Задание 2) вы проектировали REST-эндпоинты для Task Manager (CRUD для Task и Comment, смена статуса, фильтрация списка).
- (1,5 балла) Опишите на SDL типы
Task,Comment,User, корневые типыQueryиMutation, покрывающие те же возможности (минимум: получение задачи по id, список задач с фильтрами, создание задачи, смена статуса, добавление комментария). - (0,5 балла) Какие из REST-эндпоинтов из Урока 07.01 не имеют прямого аналога в виде отдельного "эндпоинта" в GraphQL-варианте и почему?
Задание 2. Over-/Under-fetching (2 балла)
Ситуация: дизайнер прислал макет экрана «Доска задач», где для каждой карточки нужны: title, status, priority, имя исполнителя и количество комментариев (не сами комментарии).
- (0,5 балла) Опишите, сколько REST-запросов потребуется для отрисовки доски из 30 задач при «классическом» REST API из Урока 07.01 (без специального batch-эндпоинта).
- (1 балл) Напишите GraphQL-запрос, который вернёт все нужные данные для доски за один запрос. (Подсказка: для количества комментариев в схему нужно будет добавить поле, агрегирующее
comments.) - (0,5 балла) Назовите одно преимущество и один риск подхода "один универсальный GraphQL-запрос для экрана" по сравнению с несколькими простыми REST-эндпоинтами.
Задание 3. N+1 problem (2 балла)
Резолвер Task.assignee реализован так: при каждом вызове делает SELECT * FROM users WHERE id = $assigneeId.
- (0,5 балла) Дан запрос, возвращающий 100 задач. Сколько всего SQL-запросов выполнится при текущей реализации (включая запрос за списком задач)?
- (1 балл) Опишите, как DataLoader изменит этот сценарий: какие запросы будут выполнены и в каком порядке.
- (0,5 балла) Почему эта проблема может остаться незамеченной на этапе тестирования (с 3 тестовыми задачами), но проявится в продакшене?
Задание 4. Ошибки и partial response (2 балла)
Клиент отправил запрос:
query {
task(id: "999") {
title
assignee { name }
}
}
Задачи с id = "999" не существует.
- (0,5 балла) Какой HTTP-статус вернёт сервер?
- (1 балл) Опишите содержимое полей
dataиerrorsв ответе. - (0,5 балла) Спроектируйте код ошибки (
extensions.code) для этого случая и опишите, как клиентское приложение должно на него реагировать в UI.
Задание 5. Чек-лист выбора технологии (2 балла)
Для каждого из сценариев выберите подход — REST, GraphQL, SOAP или gRPC — и обоснуйте в 1-2 предложениях:
| № | Сценарий | Подход | Обоснование |
|---|---|---|---|
| 1 | Публичный API банка для сторонних разработчиков с жёсткими требованиями к аудиту запросов и XML-форматом, закреплённым регулятором | ||
| 2 | Единый backend, отдающий данные для веб-дашборда, мобильного приложения и Smart TV-приложения с разными наборами полей на каждом экране | ||
| 3 | Внутренний вызов между двумя микросервисами с требованием минимальной задержки и строгой типизацией сообщений | ||
| 4 | Простой публичный API погоды с 3 эндпоинтами, который должен легко кэшироваться через CDN |
Критерии оценки
| Задание | Баллы |
|---|---|
| Задание 1: REST → GraphQL-схема | 2 |
| Задание 2: Over-/Under-fetching | 2 |
| Задание 3: N+1 problem | 2 |
| Задание 4: Ошибки и partial response | 2 |
| Задание 5: Чек-лист выбора технологии | 2 |
| Итого | 10 |
Дополнительные материалы
- Документация: graphql.org — официальная документация и спецификация GraphQL
- Инструмент: GraphiQL / Apollo Sandbox — интерактивная среда для GraphQL-запросов
- Статья: «GraphQL N+1 Problem and How to Solve It» — разбор DataLoader
- Статья: «Public GraphQL Schema Design» (Apollo blog) — практики проектирования схемы и deprecation policy
- Связанные темы: Урок 07.01 — REST, статус-коды, версионирование, rate limiting, Урок 07.02 — OpenAPI и полиморфизм, Урок 07.05 — паттерн BFF, Урок 07.07 — Webhooks