Схема и SDL: контракт между клиентом и сервером

Схема — это формальное описание всего, что можно спросить у API. Её пишут на языке SDL, и она служит строгим контрактом между клиентом и сервером.

Нет схемы — нет GraphQL. Схема — это и документация, и контракт, и валидатор одновременно. С неё начинается любой сервер.

В REST контракт API живёт где-то отдельно: в Swagger-файле, в вики, в голове бэкендера. В GraphQL контракт — это сама схема, и она часть рантайма. Сервер физически не примет запрос, который не описан в схеме. Поэтому схема не врёт: если поле есть в схеме — оно гарантированно доступно, если его нет — запрос будет отклонён ещё до резолверов.

Пишут схему на SDL — Schema Definition Language. Это компактный декларативный синтаксис: ты описываешь типы и их поля, а сервер сам строит из этого исполняемую схему. У SDL есть приятное свойство — он язык-независимый. Одну и ту же схему можно реализовать на сервере хоть на Node.js с Apollo, хоть на Python, хоть на Go или Java: SDL описывает контракт, а не реализацию. Благодаря этому фронтенд и бэкенд могут договариваться о схеме заранее, ещё до написания кода, и работать параллельно — фронт пишет запросы под согласованную схему, бэк наполняет её резолверами. Такой «schema-first» подход — одна из причин, почему GraphQL хорошо ложится на командную разработку: схема становится единым источником правды, который видят и люди, и инструменты.

Минимальная схема

type User {
  id: ID!
  name: String!
  age: Int
}

type Query {
  user(id: ID!): User
  users: [User!]!
}

Читается так: есть тип User с полями id, name, age. Есть особый тип Query — корень для всех запросов на чтение. В нём два поля: user принимает аргумент id и возвращает одного пользователя, users возвращает список пользователей. Восклицательные знаки и квадратные скобки — модификаторы типов, их разберём в отдельном уроке.

Граф типов

Название «GraphQL» не случайно: схема — это граф. Типы — узлы, поля-ссылки на другие типы — рёбра. Вот как выглядит небольшая схема блога:

        +------------------+
        |      Query       |  (корень чтения)
        +------------------+
          | user      | posts
          v           v
     +--------+    +--------+
     |  User  |<---|  Post  |
     +--------+    +--------+
      | posts       | author (ссылка назад на User)
      v             | comments
   [Post!]!         v
                [Comment!]!

Клиент, по сути, путешествует по этому графу: входит через поле Query, переходит по рёбрам (полям-ссылкам) и собирает нужное поддерево.

Как работает под капотом

При старте сервер парсит SDL-строку и строит объект схемы: словарь типов, у каждого — словарь полей, у каждого поля — его тип и аргументы. Когда приходит запрос, валидатор пробегает по нему и сверяет каждое поле с этим словарём. Несуществующее поле или неверный тип аргумента — мгновенный отказ, резолверы даже не запускаются.

Смоделируем «словарь типов» и валидацию запроса на JS:

const schema = {
  User: { fields: ["id", "name", "age"] }
};

function validate(typeName, requestedFields) {
  const known = schema[typeName].fields;
  const bad = requestedFields.filter(f => !known.includes(f));
  return bad.length ? "Ошибка: нет полей " + bad.join(", ") : "OK";
}

console.log(validate("User", ["id", "name"]));   // OK
console.log(validate("User", ["id", "salary"])); // нет полей salary

Попробуй сам ▶ — попроси поле, которого нет в схеме, и увидишь, как валидатор его ловит. Ровно это делает GraphQL-сервер до выполнения запроса.

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

  • Забыть тип Query. Без корневого типа Query схема невалидна — спрашивать будет нечего.
  • Путать схему и данные. Схема описывает форму и возможности, но не содержит самих данных — их отдают резолверы.
  • Менять схему ломающе. Удалить поле или сделать nullable-поле обязательным — это breaking change для всех клиентов. О безопасной эволюции будет отдельный разговор.

Best practices

  • Проектируй схему «от клиента»: какие экраны и сценарии есть в приложении — такие поля и связи и нужны. Схема описывает домен, а не таблицы БД.
  • Держи схему в отдельных .graphql-файлах под контролем версий — её удобно ревьюить как обычный код.
  • Имена типов — с большой буквы и в единственном числе (User, не users); имена полей — camelCase.

Итоги

Схема на языке SDL — это сердце GraphQL: строгий контракт, документация и валидатор в одном лице. Она описывает типы (узлы) и поля-связи (рёбра), образуя граф данных, по которому путешествует клиент. Корнем чтения служит тип Query. Дальше разберём строительные блоки типов — скаляры, объекты и модификаторы.

Проверьте себя
1. Что такое GraphQL-схема?
AФайл с данными API
BФормальное описание типов и полей — контракт API на языке SDL
CКонфиг базы данных
DСписок URL-эндпоинтов
2. Какой тип является корнем для всех операций чтения?
AMutation
BSubscription
CQuery
DSchema