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