Поля, псевдонимы и фрагменты
Схема Академии уже определяет Course и чтение списка. Теперь разберём документ запроса: клиент выбирает поля, может дать им псевдонимы и переиспользовать набор через фрагмент. Данные остаются прежними, поэтому изменения ответа видны без вмешательства в хранилище. Это полезная основа будущего клиента: GraphQL не возвращает один неизменный «полный объект курса», а выполняет конкретную проекцию, заданную операцией.
Пока исполнение находится внутри программы Node.js. В состоянии lesson-02 используются те же GraphQL.js 17.0.2 и схема, что в первом уроке. Мы не добавляем HTTP ради примеров синтаксиса: сетевой конверт будет отдельным шагом. Такое разделение помогает понять, является ли проблема ошибкой языка запроса, несогласованностью схемы или неправильным способом отправки документа.
Поле ответа и имя поля схемы
Псевдоним задаётся перед двоеточием. Если запросить label: title, сервер использует resolver поля title, но результат содержит ключ label. Нового поля label в схеме не появилось. Это возможность одной операции, а не глобальное переименование договора. Другой клиент по-прежнему может запросить title и получить привычный ключ. Такой приём удобен, когда форма интерфейса хочет собственное имя, сохраняя совместимость API.
Более содержательный случай — два обращения к course с разными аргументами. Без разных имён результата возникает конфликт: один ключ course не может одновременно представлять две независимые карточки. Псевдонимы first и second делают форму ответа однозначной. Язык проверяет совместимость выборок, а не просто соединяет произвольные фрагменты JSON. Поэтому понимание имён результата помогает читать и ошибки валидации.
query CoursePair {
first: course(id:"c01") { ...CourseCard }
second: course(id:"c02") { ...CourseCard }
}
fragment CourseCard on Course { id title level }
Это полный документ, который можно передать в run текущего состояния вместо начального запроса. Два результата независимы по ключам и опираются на те же исходные карточки c01 и c02. Удобно мысленно заменить второй ID на отсутствующий: second должен стать null, а first сохранит данные. Nullable корневое поле из предыдущего урока уже позволяет выразить такой ответ без выдуманного объекта.
Фрагмент как набор полей
Фрагмент объявляет именованную выборку для конкретного типа. Например, общие id, title и level можно поместить в CourseCard on Course, а затем включать в разные места документа. Это не запрос, выполняемый сам по себе, и не серверная функция. Фрагмент участвует в проверке и выполнении той операции, которая использует его. Удаление операции при сохранённом фрагменте не оставляет полноценное чтение данных.
Тип условия помогает языку понять, какие поля допустимы. Фрагмент для Course нельзя произвольно использовать на объекте другого несовместимого типа. Пока у нас нет интерфейсов и union, однако принцип уже виден: повторяемая выборка имеет определённый контекст. Слово «переиспользование» не означает снятие проверки схемы. Фрагмент сохраняет общий набор, чтобы изменения карточки интерфейса не приходилось вручную повторять в нескольких местах документа.
Не обязательно помещать все поля в один огромный фрагмент. Если список показывает только название и уровень, а отдельная страница ещё получает автора, это могут быть разные проекции. Чрезмерный общий набор заставляет каждый экран просить лишние сведения и связывает независимые интерфейсы. В нашем курсе сначала выберем маленькую карточку, затем осознанно расширим документ вложенным автором в четвёртом уроке.
Имя операции
Именованная операция query CoursePair удобна для наблюдения и повторного использования. Имя не создаёт новый маршрут HTTP и не заменяет функции root. Оно обозначает намерение конкретного документа клиента. Когда один текст содержит несколько операций, необходимо выбрать operationName. Без выбора сервер не должен случайно исполнять первую по порядку только потому, что так получилось в файле. Это явная граница, которую мы включим в будущий JSON-конверт.
Файл operations.graphql хранит примеры документов, но сервер не читает его автоматически как список разрешённых команд. Такой набор полезен человеку и будущему анализу совместимости. Считать его защитой доступа можно только после отдельной реализации выбора известных операций. Наш текущий run принимает текст документа; это другой договор. Не следует приписывать папке исходников поведение, которого нет в функции выполнения.
Порядок полей в выборке помогает читать ответ, но предметный ID не зависит от порядка. Псевдоним названия также не меняет объект хранилища: label не становится новым свойством курса в data.mjs. Если клиент изменил проекцию, он должен обновить собственное чтение ключей. Попытка получить data.course.title после запроса label:title вернёт отсутствие значения в клиентском коде, хотя сервер выполнил корректную операцию.
Неправильная и полезная выборка
Рассмотрим конфликтующие два поля course(id:"c01") и course(id:"c02") с одинаковым именем ответа. Ожидается ошибка проверки документа. Исправление — разные псевдонимы, а не изменение данных курса или создание двух корневых маршрутов. Такая ошибка находится до предметного чтения. Она объясняет, почему серверный журнал источника данных может быть пустым, хотя клиент отправлял «запрос»: выполнение допустимых resolvers ещё не началось.
Другой случай — фрагмент определён, но не используется. Язык проверяет документы как связный набор, поэтому лишние определения могут быть отклонены. Это отличается от обычного JavaScript-файла с вспомогательной функцией, которую никто не вызвал. Документ GraphQL имеет собственные правила. Лучше отправлять только те фрагменты, которые действительно составляют выбранную операцию, особенно при генерации документов сборщиком клиентского приложения.
При самостоятельном изменении добавьте псевдоним level и сохраните ID. Затем прочитайте ожидаемую форму JSON: какие ключи сменились, а какие значения остались? Такой разбор помогает проверить понимание без запуска приложения. В ходе подготовки урока примеры не выполнялись; архив содержит полное состояние, а результат описывается как ожидаемый по документу и схеме. Изменение формы ответа не считается экспериментальным замером производительности.
Следующий шаг вынесет значения ID в переменные и добавит ограниченный HTTP POST. Псевдонимы и фрагменты останутся частью строки query, а конкретные значения будут передаваться отдельным объектом variables. Благодаря этому клиент перестанет собирать текст GraphQL соединением строк, и договор между языком запроса и транспортом станет заметен в одном небольшом конверте.
Исходники урока
Оглавление серии · Скачать исходники. Полное самостоятельное состояние находится в lesson-02. Учебные приложения, зависимости, база и проверки при подготовке не запускались; описанный результат ожидается по исходникам. Материалы пока находятся в локальной редакционной версии.
Первичные источники: документация 1.