Поля, вложенность и обход дерева запроса

Запрос GraphQL — это дерево полей. Вложенность позволяет за один запрос пройти по связям между типами и собрать ровно нужное поддерево данных.

Один запрос — одно путешествие по графу. То, на что в REST уходило пять кругов по сети, здесь умещается в одну вложенную выборку.

Базовая единица запроса — поле. Скалярное поле (name, age) — лист дерева: запрашиваешь его — получаешь значение. Объектное поле (author, posts) — ветвь: чтобы его запросить, нужно открыть фигурные скобки и указать, какие поля внутри тебе интересны. Так запрос растёт в глубину, повторяя связи схемы.

Вложенная выборка

query {
  user(id: "42") {        # объект -> открываем {}
    name                  # лист
    posts {               # объект-список -> снова {}
      title               # лист
      comments {          # ещё глубже
        text
        author { name }   # переход обратно к User
      }
    }
  }
}

За один такой запрос клиент получает пользователя, его посты, комментарии к ним и авторов комментариев — всё связное дерево. В REST это была бы целая серия запросов: сходить за пользователем, потом за его постами, потом за комментариями каждого поста, потом за авторами каждого комментария. Эту лесенку кругов по сети GraphQL схлопывает в одну вложенную выборку. Заметь и обратные переходы по графу: comments.author снова приводит нас к типу User — связи в схеме могут вести в обе стороны и образовывать циклы. Именно поэтому говорят, что клиент «путешествует по графу»: он входит через корневое поле и дальше свободно переходит по рёбрам-связям, собирая ровно ту форму данных, которая нужна конкретному экрану.

Как работает обход

Сервер обходит дерево запроса в глубину (depth-first): полностью резолвит одно поле со всеми вложенными, прежде чем перейти к следующему. Значение родителя передаётся вниз детям — именно через него поле posts узнаёт, чьи посты искать.

query
  └─ user(id:42)            -> резолвер вернул { id:42, name }
       ├─ name              -> "Аня"  (лист)
       └─ posts             -> [ {id:1}, {id:2} ]  (передаём вниз user)
            ├─ title        -> "..."  (лист)
            └─ comments     -> [...]  (передаём вниз post)
                 ├─ text
                 └─ author -> name

Под капотом: рекурсивный обход на JS

Смоделируем сервер, который обходит дерево запроса и собирает ответ из вложенных данных. «Запрос» зададим как объект формы, данные — как граф:

const data = {
  user: {
    name: "Аня",
    posts: [
      { title: "Первый код", comments: [{ text: "круто!" }] },
      { title: "Учу GraphQL", comments: [] }
    ]
  }
};

// форма запроса: true = лист, объект = углубиться
const query = {
  user: { name: true, posts: { title: true } }
};

function execute(query, source) {
  const out = {};
  for (const field in query) {
    const sub = query[field];
    const val = source[field];
    if (sub === true) { out[field] = val; }
    else if (Array.isArray(val)) {
      out[field] = val.map(item => execute(sub, item));
    } else {
      out[field] = execute(sub, val);
    }
  }
  return out;
}

console.log(JSON.stringify(execute(query, data), null, 2));

Попробуй сам ▶ — добавь в posts выборку comments: { text: true } и увидишь, как ответ углубится. Это и есть суть GraphQL-движка в миниатюре.

Частые ошибки

  • Запрос объекта без вложенных полей. posts без { ... } внутри — ошибка валидации: до скаляров надо дойти.
  • Слишком глубокая вложенность. Связи бывают циклическими (user -> posts -> author -> posts -> ...), и запрос можно вложить очень глубоко. Это риск для сервера — позже разберём depth limiting.
  • Думать, что глубокий запрос «бесплатен». Каждое вложенное поле — это вызов резолвера. Глубокое дерево над списками легко рождает проблему N+1.

Best practices

  • Запрашивай только те поля, что реально нужны экрану: в этом весь смысл GraphQL, и это снижает нагрузку на сервер.
  • Для повторяющихся наборов полей используй фрагменты (о них — отдельный урок), чтобы не дублировать выборку.
  • Держи в голове, что вложенность над списками умножает работу сервера — глубокие списки внутри списков особенно дороги.

Итоги

Запрос — это дерево полей: скаляры-листья и объекты-ветви. Вложенность позволяет за один запрос пройти по связям графа и собрать нужное поддерево, заменяя серию REST-запросов. Сервер обходит дерево в глубину, передавая значение родителя детям. Дальше научимся управлять выборкой через аргументы.

Проверьте себя
1. Как запросить объектное поле, например author?
AПросто написать author
BУказать author и открыть {} с нужными вложенными полями
CДобавить ! после author
DОбернуть author в []
2. В каком порядке сервер обходит дерево запроса?
AВ ширину, по уровням
BВ глубину (depth-first): полностью резолвит поле со вложенными, потом следующее
CВ случайном порядке
DСнизу вверх