GraphQL Advanced Vocabulary: SDL, Resolvers, Federation, DataLoader і багато іншого

Освоєння розширеного словника GraphQL: мова визначення схеми, розв’ язувачі, мутації, підписки, задача N+1, DataLoader, федерація схем, федерація Apollo, тривалі запити і шаблони шлюзу API. Для інженерів, які створюють і використовують API GraphQL.

GraphQL має свій власний точний словник, який значно відрізняється від REST. Якщо ви приєднаєтеся до команди, яка використовує GraphQL — або створюєте один з тисяч відкритих і внутрішніх API GraphQL — вам слід знати цю мову.

Цей посібник містить 40 розширених термінів GraphQL, які мають найбільше значення, починаючи з мови визначення схеми і закінчуючи архітектурою федерації і шаблонами шлюзу API.


Що таке GraphQL?

** GraphQL ** — це мова запиту для API і середовище виконання для виконання цих запитів. На відміну від REST (який має фіксовану кінцеву точку, що повертає фіксовану форму даних), GraphQL дозволяє клієнтам вказувати, які саме дані їм потрібні, запитувати декілька ресурсів в одному запиті і оновлювати дані за допомогою мутацій.

GraphQL був створений Facebook / Meta в 2012 році і відкритий в 2015 році.


Мова визначення схеми (SDL)

Схема мови (Schema Language)

** Мова визначення схеми (SDL) ** — це мова, яку використовують для визначення схеми GraphQL. Він описує типи, поля, запити, мутації і підписки, які API виставляє.

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

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

“Перед написанням будь-якої логіки розв’язування, ми починаємо з SDL — схема-перший дизайн означає, що команди фронтенд і бекенд можуть погодитися на контракт перед початком реалізації.”

Тип системи

** Система типів ** GraphQL сильно типізована. У кожному полі оголошено тип. Серед типів:

  • Скалярні типи: String, Int, Float, Boolean, ID
  • ** Типи об’ єктів **: типи, визначені користувачем, з полями (наприклад, User, Post )
  • ** Типи вводу **: типи, що використовуються для аргументів мутації (наприклад, CreateUserInput )
  • ** Типи Enum **: фіксоване число дозволених значень
  • ** Типи інтерфейсів **: абстрактні типи, які можуть реалізовувати типи об’ єктів
  • ** Типи об’ єднання **: поле, яке може повертати один з декількох типів

Не нульовий (!)

Знак оклику ( ! ) в GraphQL SDL означає, що поле є non-null — воно завжди поверне значення, ніколи null.

name: String! — завжди рядок name: String — може бути нульовим

“Ми зробили email не нульовим — наші бізнес-правила гарантують, що кожен користувач має адресу електронної пошти. Зробити поля не нульовими, де це необхідно, покращує безпеку клієнта.»

Тип вводу

** Тип вводу ** — це особливий тип об’ єкта, який використовується для аргументів мутації. На відміну від звичайних типів об’ єктів, типи вводу не можуть мати розв’ язувачів — вони є чистими структурами даних.

input CreateUserInput {
  name: String!
  email: String!
  role: UserRole!
}

Enum

GraphQL ** enum ** визначає поле, яке може бути лише одним з фіксованого набору значень.

enum UserRole {
  ADMIN
  EDITOR
  VIEWER
}

Операції: запит, мутація, підписка

Query

** запит ** — це операція читання у GraphQL — отримання даних. Запити є еквівалентом запитів HTTP GET у мові GraphQL.

query {
  user(id: "123") {
    name
    email
    posts {
      title
    }
  }
}

Mutation

** Мутація ** — це операція запису — створення, оновлення або вилучення даних. Мутації є еквівалентом POST, PUT, PATCH, DELETE в REST.

mutation {
  createUser(input: { name: "Alice", email: "alice@example.com" }) {
    id
    name
  }
}
  • “Всі зміни стану проходять через мутації. Ми завжди повертаємо змінену сутність з мутації, щоб клієнт міг оновити свій кеш без окремого запиту.”*

Subscription

** підписка ** є операцією у реальному часі — клієнт підписується на потік даних і отримує оновлення, коли дані змінюються. Реалізовано через WebSockets або події, надіслані сервером.

subscription {
  messageAdded(channelId: "general") {
    id
    content
    author {
      name
    }
  }
}

Назва операції

** Назва дії ** — це додатковий ідентифікатор, який надається запиту, мутації або підписці. Рекомендується у виробничих умовах для зневадження, ведення журналу та відстеження.

query GetUserProfile {
  user(id: "123") { name }
}

Fragment

** фрагмент ** — це одиниця полів, які можна використовувати повторно і які можна вбудовувати у декілька операцій. Зменшує повторення.

fragment UserFields on User {
  id
  name
  email
}

query {
  user(id: "1") { ...UserFields }
}

Resolvers

Resolver

** розв’ язувач ** — це функція, яка заповнює дані для одного поля у схемі. Кожне поле * може * мати розв’ язувач; поля без явних розв’ язувачів використовують типовий розв’ язувач, який читається з батьківського об’ єкта.

const resolvers = {
  Query: {
    user: (parent, args, context, info) => {
      return context.db.findUserById(args.id);
    }
  }
};

Розв’язати аргументи

Розв’ язувачі отримують чотири аргументи:

  1. ** parent ** (або root ): результат батьківського розв’ язувача
  2. ** аргументи **: аргументи, передані до поля (наприклад, id: "123" )
  3. ** context **: контекст спільного запиту — інформація про розпізнавання, з’ єднання з базою даних, завантажувачі
  4. ** info **: метадані про поточне виконання (назва поля, шлях, схема)

Розв’язати ланцюг

Коли виконується запит, розв’ язувачі утворюють ** ланцюг **: спочатку виконується кореневий розв’ язувач, потім по черзі виконується розв’ язувач кожного дочірнього поля. Дочірній розв’ язувач отримує результат батьківського як свій перший аргумент.

“Поле posts на User має власний розв’язувач — він отримує об’єкт користувача як батьківський і отримує повідомлення для цього конкретного користувача.”


Проблема n + 1

Проблема n + 1

Проблема ** N+1 ** є класичною проблемою швидкодії GraphQL (і ORM): якщо ви отримаєте список з N елементів і поле кожного елемента вимагає окремого запиту до бази даних, у результаті ви отримаєте 1 запит для отримання списку + N запитів для поля — ** N+1 ** всього.

** Приклад: **

query {
  users {     # 1 query: fetch all users
    name
    posts {   # N queries: one per user to fetch their posts
      title
    }
  }
}

Для 100 користувачів це 101 запит на базу даних.

DataLoader

** DataLoader ** — це бібліотека, яка розв’ язує проблему N+1 шляхом ** пакетування і кешування ** запитів. Замість виконання запиту на кожного користувача, DataLoader збирає всі ідентифікатори користувачів, які було запрошено, у одному запиті і виконує один пакетний запит.

const userLoader = new DataLoader(async (userIds) => {
  const users = await db.getUsersByIds(userIds); // one query
  return userIds.map(id => users.find(u => u.id === id));
});
  • “До DataLoader, отримання 100 користувачів з їхніми повідомленнями робило 101 запит. Після DataLoader, це робить 2 — один для користувачів, один для всіх їх постів разом.»*

Batching

** Пакетне виконання ** (у DataLoader) — це збирання декількох окремих запитів і виконання їх як єдиної операції у одному і тому ж місці циклу подій.

Кэшування (DataLoader)

DataLoader також надає ** кешування за запитом ** — якщо одна і та ж сутність запитується двічі в одному і тому ж запиті, вона буде отримана лише один раз, а результат буде кешовано протягом часу запиту. Кеш слід очищати між запитами.


Схема федерації

Схема федерації

** Схема федерації ** — це архітектура для створення єдиної, об’ єднаної схеми GraphQL з декількох незалежних служб підграфів. Кожна команда володіє підграфом; шлюз з’єднує їх у суперграф.

  • “Кожна команда володіє своїм підграфіком GraphQL — команда користувачів володіє типами користувачів, команда розробників володіє типами продуктів. Наш шлюз об’єднує їх в один API, який клієнти запиту.”*

Supergraph

** суперграф ** є повною, складеною схемою GraphQL — об’ єднаним результатом поєднання всіх підграфів. Клієнти надсилають запити на суперграф, не знаючи, який підграф володіє якими даними.

Subgraph

** підграф ** є частиною федеративної схеми однієї з служб. Підграф визначає свої власні типи і розв’язувачі, щоб їх виконати, плюс @key директиви, щоб дозволити іншим підграфам посилатися на його сутності.

Федерація «Аполлон»

** Apollo Federation ** — це специфікація федерації схем, яку найчастіше використовують, розроблена Apollo GraphQL. Він визначає директиви (@key, @external, @requires, @provides) і архітектуру маршрутизатора/шлюз.

Директива « @ key »

Директива @key позначає унікальний ідентифікатор сутності в Apollo Federation — що дозволяє іншим підграфам посилатися і розв’язувати цю сутність.

type User @key(fields: "id") {
  id: ID!
  name: String!
}

Схема стикування

** Сплетення схем ** — це старий (і менш рекомендований) підхід до об’ єднання декількох API GraphQL у один за допомогою об’ єднання під час виконання. Загалом, федерація є кращою для нових проектів.

Шлюз / маршрутизатор

** Шлюз ** (або ** маршрутизатор ** у термінології Apollo) є точкою входу, яка отримує всі клієнтські запити, планує виконання через підграфи і збирає кінцеву відповідь.


Виробництво та операції

Постійні запити

** Персистентні запити ** (або ** автоматичні персистентні запити / APQ **) — це метод, за якого клієнт надсилає геш запиту замість повного тексту запиту. Сервер шукає повний запит за допомогою гешування, зменшуючи пропускну здатність і запобігаючи довільному виконанню запиту.

  • “Ми використовуємо постійні запити у виробничих умовах — клієнти надсилають геш запиту, а не повний SDL. Це також дозволяє нам блокувати запити, які не в нашому списку дозволених. “*

Introspection

Інтроспективність є вбудованою можливістю GraphQL запиту самої схеми — запитання “які типи і поля має цей API?” Інтроспективність забезпечує інструменти GraphQL (дослідники, генератори коду, IDE).

  • “Ми вимикаємо інтроспекцію у виробничих умовах — нападники можуть використовувати її для розуміння всієї нашої моделі даних. Інтроспекція ввімкнена в стаджінг.”*

Обмеження глибини запиту

** Обмеження глибини запиту ** запобігає надмірному вкладенню запитів (що може призвести до дорогих з’ єднань баз даних або нескінченної рекурсії у кругових схемах). Глибина 5-7 є поширеною.

Запит на аналіз вартості

** Аналіз вартості запиту ** призначає числову вартість кожному полю і відкидає запити, які перевищують порог загальної вартості — це більш складна альтернатива обмеженню глибини.

Схема дрейфу

** Дрейф схеми ** відбувається, коли фактичні розв’ язувачі відхиляються від оголошеної схеми — поля існують у схемі, але їх розв’ язувачі пошкоджені або відсутні. Інструменти реєстру схеми та інтеграційні тести виявляють дрейф.


Пам’ятник Тарасу Шевченку

API ворота

** Шлюз API ** — це зворотній проксі, який розташовується між клієнтами і серверними службами. Вона обробляє перетинні проблеми: маршрутизацію, обмеження швидкості, автентифікацію, перетворення запитів, кешування відповідей. Шлюзи GraphQL є спеціалізованою формою.

Обмеження швидкості

** Обмеження швидкості ** на рівні шлюзу обмежує кількість запитів, які клієнт може надати за певний проміжок часу. Для GraphQL, це часто реалізується за операцією або за вартістю запиту, а не за HTTP запитом (оскільки один HTTP запит може містити декілька запитів).

Керування ключами API

** Керування ключами API ** на шлюзі керує тим, які клієнти мають право використовувати API, стежить за використанням кожного ключа і уможливлює його скасування. Ключ API рівня шлюзу відрізняється від токена автентифікації користувача.

Запит на перетворення

** Перетворення запитів ** на рівні шлюзу змінює запити перед їх пересиланням до служб, що надсилають запити — додавання заголовків, нормалізація форматів, введення контексту. Деякі шлюзи можуть перетворювати відповіді REST в GraphQL.


Корисні фрази

SituationPhrase
Describing the N+1 fix”We implemented DataLoader for all entity-level field resolvers — the query count dropped from 200 to 3.”
Explaining federation”Each team publishes a subgraph that owns its domain types. The supergraph federates them automatically — clients don’t see the boundary.”
Discussing introspection”We disable introspection in production and only allow registered persisted queries to prevent schema enumeration.”
Schema evolution”Adding a new optional field to a type is backward compatible — existing clients that don’t request it are unaffected.”
Choosing REST vs. GraphQL”GraphQL is worth the complexity when clients need flexible data fetching. For simple CRUD with a single consumer, REST is simpler and better understood.”

Зв’язані ресурси

Поширені запитання

Про що ця стаття "GraphQL Advanced Vocabulary: SDL, Resolvers, Federation, DataLoader і багато іншого"?

Освоєння розширеного словника GraphQL: мова визначення схеми, розв’ язувачі, мутації, підписки, задача N+1, DataLoader, федерація схем, федерація Apollo, тривалі запити і шаблони шлюзу API. Для інженерів, які створюють і використовують API GraphQL.

Чи безкоштовна ця стаття?

Так. Усі статті на CoderSlingo, включно з цією, доступні безкоштовно без реєстрації.

Скільки часу займає читання "GraphQL Advanced Vocabulary: SDL, Resolvers, Federation, DataLoader і багато іншого"?

Приблизно 13 min.