Как устроен язык запросов 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:
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 и добавлять новые типы данных. При этом есть возможность повторного использования существующего кода и источников данных, чтобы избежать случаев избыточного кода.