Журнал / польза

Как устроен язык запросов GraphQL

GraphQL — лаконичный язык запросов, который ускоряет разработку благодаря тому, что позволяет клиенту запрашивать только нужные данные

Что такое GraphQL

GraphQL — это язык запросов и серверная среда для API с открытым исходным кодом. Он появился в Facebook в 2012 году и был разработан для упрощения управления конечными точками для API на основе REST.

Когда разработчики Facebook создавали мобильное приложение, они искали способы ускорить работу. Была трудность: при одновременном запросе из различных по типу баз данных, например из облачной Redis и MySQL, приложение ужасно тормозило. Для решения задачи в Facebook придумали собственный язык запросов, который обращается к конечной точке и упрощает форму запрашиваемых данных.

В 2015 году код GraphQL стал открытым, а в 2018 был передан организации Linux Foundation, которая занимается поддержкой опенсорс-проектов. Сейчас GraphQL используют Airbnb, GitHub, Pinterest, Shopify.

Как работает GraphQL

GraphQL оставляет разработчикам свободу действий. У него нет указаний, как хранить данные или какой язык программирования использовать. Серверы GraphQL предназначены для работы с JavaScript, Python, Ruby, C#, Go и PHP. Для них созданы библиотеки graphql-php (PHP), Graphene-Phyton (Python), graphql-ruby (Ruby), graphql.js (JavaScript) и другие.

Базовый синтаксис GraphQL включает в себя:

  • поля (Fields);
  • аргументы (Arguments);
  • фрагменты (Fragments);
  • псевдонимы (Aliases);
  • переменные (Variables);
  • директивы (Directives).

Работа GraphQL построена на запросах и ответах. Запрос — набор инструкций, которые указывают серверу, какие данные нужны. Ответ — запрошенные данные в формате JSON или XML. Буквы QL в названии GraphQL означают query language: это буквально новый язык написания запросов на получение данных.

Поля в запросах действуют как функция, которая возвращает следующий тип поля, и следующий тип, и следующий тип, пока возвращаемое поле не будет скаляром, например строкой или логическим значением. Все запросы начинаются с «корневого» объекта. Например, запрашиваем hero:

{
  hero {
    name
    appearsIn
  }
}

Для него указываем вложенные поля name и appearsIn, которые нужно вернуть для этого объекта. В результате получаем:

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ]
    }
  }
}

В ответах GraphQL по сравнению с REST нет ничего лишнего: не загрузится больше информации, чем требуется. Это делает GraphQL предсказуемым. Например, выполним такой запрос:

query {
  user(id: 123) {
    name
    email
    age
  }
}

В ответ мы получим данные о пользователе 123, идентификатор которого (id) мы жёстко задали в коде, включая его имя, электронную почту и возраст. Формат ответа зависит от того, как настроен сервер GraphQL, но чаще это JSON и выглядит он так:

{
  "data": {
    "user": {
      "name": "John Doe",
      "email": "johndoe@example.com",
      "age": 30
    }
  }
}

Аргументы в GraphQL помогают получить конкретный объект из множества. Например, указываем, сколько подписчиков мы хотим вернуть API:

{
  user(id: "1") {
    name
    followers(limit: 50)
  } 
}

Аргументом в этом запросе выступает id. Также GraphQL поддерживает более сложные запросы с фрагментами:

{
  leftComparison: user (id: 1) {
    ...comparisonFields
  }
  rightComparison: user (id: 2) {
    ...comparisonFields
  }
}

fragment UserInfo on User {
  name
  email
}

Далее фрагмент может быть использован в запросе:

query {
  user(id: 123) {
    ...UserInfo
    age
  }
}

Этот запрос использует фрагмент UserInfo для отсылки к имени, возрасту и электронной почте пользователя с идентификатором 123. Фрагменты позволяют создать структуру с разными полями. Их используют для разделения сложных данных на мелкие порции, а также чтобы создать выборку из множества компонентов пользовательского интерфейса.

Псевдонимы пригодятся, чтобы запросить несколько полей одного типа данных, но с разными значениями аргументов. Это механизм GraphQL, который позволяет задавать альтернативные имена для полей в запросах и вернуть поле с другим именем. Например, если есть два поля с одинаковым именем user, но с разными аргументами id, мы можем использовать псевдонимы, чтобы явно указать, какое поле нужно:

query {
  john: user(id: 123) {
    name
    email
  }
  jane: user(id: 456) {
    name
    email
  }
}

В этом примере мы используем псевдонимы john и jane, чтобы запросить данные о двух пользователях с разными идентификаторами. Обратите внимание, что мы запрашиваем одни и те же поля (name и email) для обоих пользователей. Два поля user могли конфликтовать, но поскольку мы присвоили им псевдонимы для разных имён, получаем оба результата в одном запросе. Он выглядит так:

{
  "data": {
    "john": {
      "name": "John Doe",
      "email": "johndoe@example.com"
    },
    "jane": {
      "name": "Jane Smith",
      "email": "janesmith@example.com"
    }
  }
}

Переменные в GraphQL динамически меняют значения внутри запроса и заменяют статическое значение. Так мы передаём параметры в запросы. Использование переменных делает запросы удобными для повторного использования. Внутри строки запроса такие аргументы передаются со знаком $. Например:

query getUser($id: Int!) {
  user(id: $id) {
    name
    email
  }
}

В этом примере мы определяем переменную $id и её тип Int!, а getUser — именованная функция. Их полезно использовать, когда в приложении много запросов. Чтобы передать значение переменной в запрос, мы выполняем на стороне клиента:

{
  "id": 123
}

Здесь мы передаём значение 123 для переменной $id. Затем мы выполняем запрос и передаём значение переменной:

{
  "query": "query getUser($id: Int!) { user(id: $id) { name email } }",
  "variables": {
    "id": 123
  }
}

В примере передаём запрос в виде строки и значение переменной в объекте variables. При выполнении запроса GraphQL заменит $id на значение 123, которое мы передали в переменной.

Директивы в GraphQL — это специальные инструкции, которые позволяют контролировать выполнение запросов и изменять их результаты. Директивы представляют собой аннотации, которые можно применять к полям, фрагментам, операциям и аргументам. В GraphQL доступны две: @include и @skip.

Например:

query getUser($id: Int!, $withEmail: Boolean!) {
  user(id: $id) {
    name
    email @include(if: $withEmail)
  }
}

Здесь используем директиву @include, чтобы включить поле email только в том случае, если значение переменной $withEmail равно true. Чтобы передать значения переменных в запрос, мы пишем:

{
  "id": 123,
  "withEmail": true
}

В результате выполнения запроса мы получим данные о пользователе, включая поле email, только если значение переменной $withEmail равно true.

Почему хорошо использовать GraphQL

  • Гибкость. GraphQL не накладывает ограничений на типы запросов, что позволяет использовать его как для традиционных CRUD-операций (create, read, update, delete), так и для запросов, которые содержат несколько типов данных.
  • Определение схемы. GraphQL автоматически создаёт схему для API. А за счёт иерархической организации кода и объектных отношений снижается его сложность.
  • Оптимизация запросов. GraphQL позволяет клиентам запрашивать только ту информацию, что им нужна. Это уменьшает время ответа от сервера и количество данных, которые нужно передавать по сети.
  • Контекст. GraphQL учитывает все детали запросов и ответов, что позволяет разработчикам фокусироваться на бизнес-логике. А строго типизированные поля предупреждают разработчиков об ошибках перед выполнением запроса.
  • Расширяемость. GraphQL позволяет разработчикам расширять схему API и добавлять новые типы данных. При этом есть возможность повторного использования существующего кода и источников данных, чтобы избежать случаев избыточного кода.