обзор
GraphQL — это новый и интересный API для специальных запросов и манипуляций. Это чрезвычайно гибкий и дает много преимуществ. Это особенно подходит для демонстрации данных, организованных в виде графиков и деревьев. Facebook разработал GraphQL в 2012 году и открыл его в 2015 году.
Он быстро взлетел и стал одной из самых горячих технологий. Многие инновационные компании приняли и использовали GraphQL в производстве. В этом уроке вы узнаете:
- принципы GraphQL
- как это сравнивается с отдыхом
- как разрабатывать схемы
- как настроить сервер GraphQL
- как реализовать запросы и мутации
- и несколько дополнительных продвинутых тем
Где сияет GraphQL?
GraphQL лучше всего работает, когда ваши данные организованы в виде иерархии или графика, и передний конец хотел бы получить доступ к различным подмножествам этой иерархии или графика. Рассмотрим приложение, которое разоблачает НБА. У вас есть команды, игроки, тренеры, чемпионаты и много информации о каждой. Вот несколько примеров запросов:
- Как зовут игроков в текущем списке Воинов Голден Стэйт?
- Каковы имена, высоты и возрасты заклинателей Washington Wizards?
- Какой активный тренер имеет больше всего чемпионатов?
- Для каких команд и в какие годы тренер выиграл свой чемпионат?
- Какой игрок выиграл больше всего наград MVP?
Я мог бы придумать сотни таких запросов. Представьте, что вам нужно спроектировать API, чтобы предоставить все эти запросы внешнему интерфейсу и иметь возможность легко расширять API новыми типами запросов, когда ваши пользователи или менеджер по продукту придумывают новые интересные вещи для запроса.
Это не тривиально. GraphQL был разработан для решения именно этой проблемы, и с одной конечной точкой API он обеспечивает огромную мощность, как вы скоро увидите.
GraphQL против REST
Прежде чем углубляться в основные моменты GraphQL, давайте сравним его с REST, который в настоящее время является наиболее популярным типом веб-API.
REST следует ресурс-ориентированной модели. Если нашими ресурсами являются игроки, тренеры и команды, то, вероятно, будут конечные точки, такие как:
- / игроков
- / игроков / <идентификатор>
- / тренеры
- / тренеры / <идентификатор>
- / команды
- / команды / <идентификатор>
Часто конечные точки без идентификатора просто возвращают список идентификаторов, а конечные точки с идентификатором возвращают полную информацию об одном ресурсе. Разумеется, вы можете разработать свой API другими способами (например, конечная точка / Players может также возвращать имя каждого игрока или всю информацию о каждом игроке).
Проблема с этим подходом в динамической среде заключается в том, что вы либо недостаточно загружены (например, вы получаете только идентификаторы, и вам нужна дополнительная информация), либо перегружаетесь (например, получаете полную информацию о каждом игроке, когда вы просто интересует имя).
Это тяжелые проблемы. При неполной загрузке, если вы получаете 100 идентификаторов, вам нужно выполнить 100 отдельных вызовов API, чтобы получить информацию о каждом игроке. При избыточной загрузке вы тратите много времени на серверную часть и пропускную способность сети, подготавливая и передавая множество ненужных данных.
Есть способы решить эту проблему с помощью REST. Вы можете создать множество индивидуальных конечных точек, каждая из которых возвращает именно те данные, которые вам нужны. Это решение не масштабируется. Трудно поддерживать согласованность API. Это сложно развивать. Это трудно документировать и использовать. Трудно поддерживать его, когда между этими конечными точками много общего.
Рассмотрим эти дополнительные конечные точки:
- / игроков / имена
- / игроков / names_and_championships
- / команда / стартеры
Другой подход состоит в том, чтобы сохранить небольшое количество общих конечных точек, но предоставить много параметров запроса. Это решение позволяет избежать проблемы со многими конечными точками, но оно идет вразрез с моделью REST, а также его сложно постоянно развивать и поддерживать.
Можно сказать, что GraphQL довел этот подход до предела. Это не мысли в терминах четко определенных ресурсов, а в терминах подграфов всего домена.
Система типов GraphQL
GraphQL моделирует домен, используя систему типов, которая состоит из типов и атрибутов. Каждый атрибут имеет тип. Тип атрибута может быть одним из основных типов, которые GraphQL предоставляет, например, ID, String и Boolean, или пользовательским типом. Узлы графа являются пользовательскими типами, а ребра являются атрибутами, которые имеют пользовательские типы.
Например, если тип «Player» имеет атрибут «team» с типом «Team», то это означает, что между каждым узлом игрока и узлом команды есть грань. Все типы определены в схеме, описывающей объектную модель домена GraphQL.
Вот очень упрощенная схема для домена NBA. У игрока есть имя, команда, с которой он больше всего ассоциируется (да, я знаю, что игроки иногда переходят из одной команды в другую) и количество чемпионатов, которые выиграл игрок.
У команды есть название, множество игроков и количество чемпионатов, которые команда выиграла.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
type Player {
id: ID
name: String!
team: Team!
championshipCount: Integer!
}
type Team {
id: ID
name: String!
players: [Player!]!
championshipCount: Integer!
}
|
Есть также предопределенные точки входа. Это запрос, мутация и подписка. Передний конец связывается с задним концом через точки входа и настраивает их для своих нужд.
Вот запрос, который просто возвращает всех игроков:
1
2
3
|
type Query {
allPlayers: [Player!]!
}
|
Восклицательный знак означает, что значение не может быть нулевым. В случае запроса allPlayers
он может вернуть пустой список, но не ноль. Кроме того, это означает, что в списке не может быть нулевого игрока (потому что он содержит Player!).
Настройка сервера GraphQL
Вот полноценный сервер GraphQL на основе node-express. Он имеет жестко запрограммированное хранилище данных в памяти. Обычно данные находятся в базе данных или извлекаются из другого сервиса. Данные определены здесь (заранее извиняюсь, если ваша любимая команда или игрок не сделали это):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
let data = {
«allPlayers»: {
«1»: {
«id»: «1»,
«name»: «Stephen Curry»,
«championshipCount»: 2,
«teamId»: «3»
},
«2»: {
«id»: «2»,
«name»: «Michael Jordan»,
«championshipCount»: 6,
«teamId»: «1»
},
«3»: {
«id»: «3»,
«name»: «Scottie Pippen»,
«championshipCount»: 6,
«teamId»: «1»
},
«4»: {
«id»: «4»,
«name»: «Magic Johnson»,
«championshipCount»: 5,
«teamId»: «2»
},
«5»: {
«id»: «5»,
«name»: «Kobe Bryant»,
«championshipCount»: 5,
«teamId»: «2»
},
«6»: {
«id»: «6»,
«name»: «Kevin Durant»,
«championshipCount»: 1,
«teamId»: «3»
}
},
«allTeams»: {
«1»: {
«id»: «1»,
«name»: «Chicago Bulls»,
«championshipCount»: 6,
«players»: []
},
«2»: {
«id»: «2»,
«name»: «Los Angeles Lakers»,
«championshipCount»: 16,
«players»: []
},
«3»: {
«id»: «3»,
«name»: «Golden State Warriors»,
«championshipCount»: 5,
«players»: []
}
}
}
|
Библиотеки, которые я использую:
1
2
3
4
5
|
const express = require(‘express’);
const graphqlHTTP = require(‘express-graphql’);
const app = express();
const { buildSchema } = require(‘graphql’);
const _ = require(‘lodash/core’);
|
Это код для построения схемы. Обратите внимание, что я добавил пару переменных в корневой запрос allPlayers
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
schema = buildSchema(`
type Player {
id: ID
name: String!
championshipCount: Int!
team: Team!
}
type Team {
id: ID
name: String!
championshipCount: Int!
players: [Player!]!
}
type Query {
allPlayers(offset: Int = 0, limit: Int = -1): [Player!]!
}`
|
Здесь начинается ключевая часть: подключение запросов и фактическая обработка данных. Объект rootValue
может содержать несколько корней.
Здесь есть только все allPlayers
. Он извлекает смещение и лимит из аргументов, разрезает данные всех игроков, а затем устанавливает команду для каждого игрока на основе идентификатора команды. Это делает каждого игрока вложенным объектом.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
rootValue = {
allPlayers: (args) => {
offset = args[‘offset’]
limit = args[‘limit’]
r = _.values(data[«allPlayers»]).slice(offset)
if (limit > -1) {
r = r.slice(0, Math.min(limit, r.length))
}
_.forEach(r, (x) => {
data.allPlayers[x.id].team = data.allTeams[x.teamId]
})
return r
},
}
|
Наконец, вот graphql
точка graphql
, передающая схему и объект корневого значения:
1
2
3
4
5
6
7
8
9
|
app.use(‘/graphql’, graphqlHTTP({
schema: schema,
rootValue: rootValue,
graphiql: true
}));
app.listen(3000);
module.exports = app;
|
Установка graphiql
для graphiql
позволяет нам тестировать сервер с помощью великолепной встроенной в браузер среды GraphQL IDE. Я настоятельно рекомендовал его для экспериментов с различными запросами.
Специальные запросы с GraphQL
Все установлено. Давайте перейдем к http: // localhost: 3000 / graphql и повеселимся.
Мы можем начать с простого списка имен игроков:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
query justNames {
allPlayers {
name
}
}
Output:
{
«data»: {
«allPlayers»: [
{
«name»: «Stephen Curry»
},
{
«name»: «Michael Jordan»
},
{
«name»: «Scottie Pippen»
},
{
«name»: «Magic Johnson»
},
{
«name»: «Kobe Bryant»
},
{
«name»: «Kevin Durant»
}
]
}
}
|
Хорошо. У нас здесь есть несколько суперзвезд. Несомненно. Давайте пойдем на что-нибудь более причудливое: начиная со смещения 4 получим 2 игрока. Для каждого игрока укажите его имя и количество чемпионатов, которые он выиграл, а также название команды и количество чемпионатов, которые команда выиграла.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
query twoPlayers {
allPlayers(offset: 4, limit: 2) {
name
championshipCount
team {
name
championshipCount
}
}
}
Output:
{
«data»: {
«allPlayers»: [
{
«name»: «Kobe Bryant»,
«championshipCount»: 5,
«team»: {
«name»: «Los Angeles Lakers»,
«championshipCount»: 16
}
},
{
«name»: «Kevin Durant»,
«championshipCount»: 1,
«team»: {
«name»: «Golden State Warriors»,
«championshipCount»: 5
}
}
]
}
}
|
Таким образом, Коби Брайант выиграл пять чемпионатов с Лейкерс, который выиграл 16 чемпионатов в целом. Кевин Дюрант выиграл всего один чемпионат с Воинами, которые выиграли всего пять чемпионатов.
GraphQL мутации
Мэджик Джонсон был волшебником на площадке наверняка. Но он не смог бы сделать это без своего приятеля Карима Абдул-Джаббара. Давайте добавим Карим в нашу базу данных. Мы можем определить мутации GraphQL для выполнения таких операций, как добавление, обновление и удаление данных из нашего графа.
Сначала давайте добавим тип мутации в схему. Это немного похоже на сигнатуру функции:
1
2
3
4
5
|
type Mutation {
createPlayer(name: String,
championshipCount: Int,
teamId: String): Player
}
|
Затем нам нужно реализовать его и добавить к корневому значению. Реализация просто берет параметры, предоставленные запросом, и добавляет новый объект к data['allPlayers']
. Это также гарантирует правильную настройку команды. Наконец, возвращается новый игрок.
1
2
3
4
5
6
7
|
createPlayer: (args) => {
id = (_.values(data[‘allPlayers’]).length + 1).toString()
args[‘id’] = id
args[‘team’] = data[‘allTeams’][args[‘teamId’]]
data[‘allPlayers’][id] = args
return data[‘allPlayers’][id]
},
|
Чтобы фактически добавить Kareem, мы можем вызвать мутацию и запросить возвращенного игрока:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
mutation addKareem {
createPlayer(name: «Kareem Abdul-Jabbar»,
championshipCount: 6,
teamId: «2») {
name
championshipCount
team {
name
}
}
}
Output:
{
«data»: {
«createPlayer»: {
«name»: «Kareem Abdul-Jabbar»,
«championshipCount»: 6,
«team»: {
«name»: «Los Angeles Lakers»
}
}
}
}
|
Вот темный маленький секрет о мутациях … на самом деле они точно такие же, как запросы. Вы можете изменить свои данные в запросе, и вы можете просто вернуть данные из мутации. GraphQL не собирается заглядывать в ваш код. И запросы, и мутации могут принимать аргументы и возвращать данные. Это больше похоже на синтаксический сахар, чтобы сделать вашу схему более читабельной.
Расширенные темы
Подписки
Подписки — еще одна убийственная особенность GraphQL. С помощью подписок клиент может подписаться на события, которые будут срабатывать при каждом изменении состояния сервера. Подписки были введены на более позднем этапе и реализуются различными структурами по-разному.
Проверка
GraphQL проверит каждый запрос или мутацию по схеме. Это большой выигрыш, когда входные данные имеют сложную форму. Вам не нужно писать раздражающий и хрупкий код проверки. GraphQL позаботится об этом за вас.
Схема самоанализа
Вы можете проверить и запросить текущую схему самой. Это дает вам мета-силы для динамического обнаружения схемы. Вот запрос, который возвращает все имена типов и их описание:
1
2
3
4
5
6
7
|
query q {
__schema {
types {
name
description
}
}
|
Вывод
GraphQL — это новая захватывающая технология API, которая предоставляет множество преимуществ по сравнению с REST API. За этим стоит активное сообщество, не говоря уже о Facebook. Я предсказываю, что он быстро станет основным продуктом. Попробуйте. Тебе понравится.