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

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

Что такое GraphQL

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

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

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

foto1

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

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

foto2

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

  • поля (Fields);

  • аргументы (Arguments);

  • фрагменты (Fragments);

  • псевдонимы (Aliases);

  • переменные (Variables);

  • директивы (Directives).

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

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

1{
2  hero {
3    name
4    appearsIn
5  }
6}

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

1{
2  "data": {
3    "hero": {
4      "name": "R2-D2",
5      "appearsIn": [
6        "NEWHOPE",
7        "EMPIRE",
8        "JEDI"
9      ]
10    }
11  }
12}

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

1query {
2  user(id: 123) {
3    name
4    email
5    age
6  }
7}

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

1{
2  "data": {
3    "user": {
4      "name": "John Doe",
5      "email": "johndoe@example.com",
6      "age": 30
7    }
8  }
9}

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

1{
2  user(id: "1") {
3    name
4    followers(limit: 50)
5  } 
6}

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

1{
2  leftComparison: user (id: 1) {
3    ...comparisonFields
4  }
5  rightComparison: user (id: 2) {
6    ...comparisonFields
7  }
8}
9
10fragment UserInfo on User {
11  name
12  email
13}

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

1query {
2  user(id: 123) {
3    ...UserInfo
4    age
5  }
6}

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

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

1query {
2  john: user(id: 123) {
3    name
4    email
5  }
6  jane: user(id: 456) {
7    name
8    email
9  }
10}

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

1{
2  "data": {
3    "john": {
4      "name": "John Doe",
5      "email": "johndoe@example.com"
6    },
7    "jane": {
8      "name": "Jane Smith",
9      "email": "janesmith@example.com"
10    }
11  }
12}

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

1query getUser($id: Int!) {
2  user(id: $id) {
3    name
4    email
5  }
6}

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

1{
2  "id": 123
3}

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

1{
2  "query": "query getUser($id: Int!) { user(id: $id) { name email } }",
3  "variables": {
4    "id": 123
5  }
6}

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

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

Например:

1query getUser($id: Int!, $withEmail: Boolean!) {
2  user(id: $id) {
3    name
4    email @include(if: $withEmail)
5  }
6}

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

1{
2  "id": 123,
3  "withEmail": true
4}

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

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

  • Гибкость. GraphQL не накладывает ограничений на типы запросов, что позволяет использовать его как для традиционных CRUD-операций (create, read, update, delete), так и для запросов, которые содержат несколько типов данных.

  • Определение схемы. GraphQL автоматически создаёт схему для API. А за счёт иерархической организации кода и объектных отношений снижается его сложность.

  • Оптимизация запросов. GraphQL позволяет клиентам запрашивать только ту информацию, что им нужна. Это уменьшает время ответа от сервера и количество данных, которые нужно передавать по сети.

  • Контекст. GraphQL учитывает все детали запросов и ответов, что позволяет разработчикам фокусироваться на бизнес-логике. А строго типизированные поля предупреждают разработчиков об ошибках перед выполнением запроса.

  • Расширяемость. GraphQL позволяет разработчикам расширять схему API и добавлять новые типы данных. При этом есть возможность повторного использования существующего кода и источников данных, чтобы избежать случаев избыточного кода.

Краткий пересказ от YandexGPT