В этой статье сначала рассказывается о GraphQL, а затем применяется GraphQL через комбинацию MongoDB + graphql + graph-pack, а также подробно рассказывается о том, как использовать GraphQL для добавления, удаления, изменения и подписки на push-данные, с примерами использования, и я впечатлен обучением во время его использования ~
Если вы хотите применить GraphQL в производственной среде, где интерфейс и сервер разделены, ждите следующей статьи.
Пример кода для этой статьи:Github
Заинтересованные студенты могут добавить группу WeChat в конце статьи для совместного обсуждения~
0. Что такое GraphQL
GraphQL — это стиль запросов API, ориентированный на данные.
Традиционный API получает формат данных, согласованный между интерфейсом и сервером. GraphQL предоставляет полное и простое для понимания описание данных в API. Клиент может точно получать нужные ему данные без какой-либо избыточности. время и создавать мощные инструменты разработчика.
1 Обзор
С популяризацией фреймворка SPA во фронтенд-разработке общей тенденцией стала и компонентная разработка, каждый компонент управляет своим собственным состоянием, компонентная привносит удобство во фронтенд, а также приносит некоторые неприятности. Например, компонент должен отвечать за распределение статуса асинхронных запросов дочерним компонентам или уведомление родительских компонентов.В этом процессе структурная сложность, вызванная связью между компонентами, источником данных неизвестного происхождения и ответом данных от который вы не знаете, где подписаться сделает Поток данных становится неорганизованным, что также делает код менее читабельным и ремонтопригодным, что приносит большие трудности в будущей итерации проекта.
Представьте, что вы закончили разработку продукта, а продукт говорит вам сделать большое изменение, от интерфейса до структуры компонента, а бэкенд тоже ругается и не желает с вами сотрудничать, чтобы получить данные из нескольких API и объединить их себе, что кисло 😅
В некоторых сценариях со сложными цепочками продуктов серверная часть должна предоставлять API, соответствующие WebApp, WebPC, APP, апплету и быстрому приложению. В настоящее время особенно важна степень детализации API. Грубая степень детализации приведет к ненужному трафику на мобильный терминал. Утрата мелких деталей приведет к взрыву функции (Function Explosion); в этом сценарии инженеры Facebook открыли исходный код в 2015 году.GraphQLСпецификация, пусть внешний интерфейс описывает форму данных, которую он хочет, а сервер возвращает структуру данных, описанную внешним интерфейсом.
Простое использование может относиться к следующему изображению:
Например, внешний интерфейс хочет вернуть идентификатор233
имя и пол пользователя, и найдите имена и адреса электронной почты первых десяти сотрудников этого пользователя, затем найдите номер телефона отца этого человека и имя собаки этого отца (не спрашивайте меня, почему у меня такие странный поиск 🤪), то мы можем получить всю информацию через запрос GraphQL, без необходимости переходить туда и обратно из нескольких асинхронных API:
query {
user (id : "233") {
name
gender
employee (first: 10) {
name
email
}
father {
telephone
dog {
name
}
}
}
}
Возвращаемый формат данных оказывается форматом данных, предоставленным внешним интерфейсом, не более и не менее, вы взволнованы 😏
2. Несколько важных концепций
Вот несколько концепций, которые важны для понимания GraphQL, другие более сложные способы использования, такие как директивы, типы объединения, встроенные фрагменты и т. д., относятся кGraphQLОфициальная документация сайта ~
2.1 Тип операции
Типы операций GraphQL могут бытьquery
,mutation
илиsubscription
, который описывает, что клиент хочет сделать
- запрос запроса: получить данные, такие как поиск, R в CRUD
- Изменение мутации: внесение изменений в данные, например добавление, удаление, изменение, CUD в CRUD.
- подписка на подписку: push-уведомления при изменении данных
Эти типы операций будут фактически использоваться позже, например, здесь выполняется операция запроса
query {
user { id }
}
2.2 Тип объекта и скалярный тип
Если служба GraphQL получаетquery
, то этоquery
будет изRoot Query
Запустите поиск, когда тип объекта (Object Type) будет найден, используйте его функцию-преобразователь Resolver для получения содержимого, если возвращается тип объекта, продолжайте использовать функцию-преобразователь для получения содержимого, если возвращается скалярный тип (Скалярный тип), завершить получение, пока не будет найден последний скалярный тип.
- Тип объекта: определяется пользователем в схеме
type
- Скалярные типы: в GraphQL встроено несколько скалярных типов.
String
,Int
,Float
,Boolean
,ID
, пользователи также могут определять свои собственные скалярные типы
Например, в объявлении схемы
type User {
name: String!
age: Int
}
этоUser
Тип объекта имеет два поля, поле имени представляет собойString
— скаляр, отличный от нуля, поле age — этоInt
Обнуляемый скаляр .
2.3 Схема Схема
Если вы использовали MongoOSE, вы должны быть знакомы с концепцией Schema, что переводится как «схема».
Он определяет тип полей, структуру данных и правила запроса данных интерфейса.Когда мы делаем какие-то неправильные запросы, механизм GraphQL будет отвечать за сообщение нам, где есть проблема, и подробную информацию об ошибке, что очень удобно. к разработке и отладке.
Схема использует простой строго типизированный синтаксис схемы, называемый языком определения схемы (SDL). Мы можем использовать реальный пример, чтобы показать, как реальный файл схемы написан на SDL:
# src/schema.graphql
# Query 入口
type Query {
hello: String
users: [User]!
user(id: String): [User]!
}
# Mutation 入口
type Mutation {
createUser(id: ID!, name: String!, email: String!, age: Int,gender: Gender): User!
updateUser(id: ID!, name: String, email: String, age: Int, gender: Gender): User!
deleteUser(id: ID!): User
}
# Subscription 入口
type Subscription {
subsUser(id: ID!): User
}
type User implements UserInterface {
id: ID!
name: String!
age: Int
gender: Gender
email: String!
}
# 枚举类型
enum Gender {
MAN
WOMAN
}
# 接口类型
interface UserInterface {
id: ID!
name: String!
age: Int
gender: Gender
}
Этот простой файл схемы определяет различные типы объектов или скалярные типы, начиная с записей запроса, мутации и подписки. Типы этих полей также могут быть другими типами объектов или скалярными типами, формируя древовидную структуру. наборы информации можно получить, выбрав одну или несколько ветвей дерева.
Уведомление:Когда Query запрашивает поля, он выполняется параллельно, а когда Mutation изменяется, он выполняется линейно, одно за другим, чтобы предотвратить состояние гонки, вызванное одновременными изменениями. Например, если мы отправляем две мутации в одном запросе, первая всегда будет выполняться раньше второй.
2.4 Резольвер аналитической функции
После того, как информация о внешнем запросе достигает серверной части, ее необходимо разрешить с помощью функции синтаксического анализа.Resolverдля предоставления данных, таких как запрос, подобный этому:
query {
hello
}
Тогда одноименная функция разбора должна быть такой
Query: {
hello (parent, args, context, info) {
return ...
}
}
Аналитическая функция принимает четыре параметра, которые
-
parent
: возвращаемое значение текущей последней функции синтаксического анализа. -
args
: Параметры, включенные в запрос -
context
: контекстная информация предоставляется всем парсерам -
info
: значение, которое содержит информацию о конкретном поле, связанную с текущим запросом, а также сведения о схеме.
Возвращаемое значение функции распознавателя может быть конкретным значением, либо обещанием, либо массивом обещаний.
Некоторые распространенные решения, такие как Apollo, могут помочь исключить некоторые простые функции синтаксического анализа.Например, когда поле не предоставляет соответствующую функцию синтаксического анализа, оно будет считывать и возвращать свойство с тем же именем, что и у поля, из возвращаемого объекта верхнего уровня.
2.5 Формат запроса
Наиболее распространенным GraphQL является отправка запросов через HTTP, так как же общаться с GraphQL через HTTP?
Поднимите каштан, как выполнить следующий запрос graphql через метод Get/Post?
query {
me {
name
}
}
Get — поместить содержимое запроса в URL, Post — вcontent-type: application/json
В этом случае поместите содержимое в формате JSON в тело запроса.
# Get 方式
http://myapi/graphql?query={me{name}}
# Post 方式的请求体
{
"query": "...",
"operationName": "...",
"variables": { "myVariable": "someValue", ... }
}
Возвращаемый формат, как правило, также является телом JSON.
# 正确返回
{
"data": { ... }
}
# 执行时发生错误
{
"errors": [ ... ]
}
Если во время выполнения возникает ошибка, массив ошибок содержит подробную информацию об ошибке, такую как информация об ошибке, местоположение ошибки и стек вызовов сцены, выдающей ошибку, что удобно для обнаружения.
3. Настоящий бой
используется здесьMongoDB + graph-packПроведите простой настоящий бой и изучите его вместе в реальном бою. Подробности смотрите в коде.Github ~
MongoDB — это NoSQL, который широко используется, и в сообществе легко найти множество готовых решений, и легко найти решения, если сообщается об ошибке.
graph-pack — сервисная среда GraphQL с нулевой конфигурацией, объединяющая Webpack + Express + Prisma + Babel + Apollo-сервер + Websocket и поддерживающая горячее обновление, здесь она используется для демонстрации использования GraphQL.
3.1 Развертывание среды
Первым делом запускаем MongoDB, этот процесс повторяться не будет, в интернете много туториалов;
Взгляните на среду графического пакета
npm i -S graphpack
существуетpackage.json
изscripts
Поля плюс:
"scripts": {
"dev": "graphpack",
"build": "graphpack build"
}
Создайте файловую структуру:
.
├── src
│ ├── db // 数据库操作相关
│ │ ├── connect.js // 数据库操作封装
│ │ ├── index.js // DAO 层
│ │ └── setting.js // 配置
│ ├── resolvers // resolvers
│ │ └── index.js
│ └── schema.graphql // schema
└── package.json
здесьschema.graphql
пример кода в Разделе 2.3, для других реализаций см.Github,Основная проблемаsrc/db
,src/resolvers
,src/schema.graphql
эти три места
-
src/db
: уровень операций с базой данных, включая уровень DAO и уровень службы (если вы мало знаете о многоуровневости, вы можете прочитать последнюю главу). -
src/resolvers
: функциональный уровень синтаксического анализа преобразователя, который предоставляет функции синтаксического анализа преобразователя для запросов Query, Mutation и Subscription GraphQL. -
src/schema.graphql
: Слой схемы
потомnpm run dev
, браузер открываетсяhttp://localhost:4000/
Вы можете использовать GraphQL Playground, чтобы начать отладку. Слева находится панель информации о запросе, внизу слева — панель параметров запроса и панель настройки заголовка запроса, а справа — панель параметров возврата. Подробное использование см.Документация Призмы
3.2 Query
Сначала попробуемhello world
,мы вschema.graphql
Напишите запись для запроса вhello
, который принимает возвращаемое значение типа String
# src/schema.graphql
# Query 入口
type Query {
hello: String
}
существуетsrc/resolvers/index.js
Соответствующий Resolver дополняется в Resolver.Этот Resolver относительно прост и возвращает String напрямую.
// src/resolvers/index.js
export default {
Query: {
hello: () => 'Hello world!'
}
}
Мы играем в процессе в запросе
# 请求
query {
hello
}
# 返回值
{
"data": {
"hello": "Hello world!"
}
}
Привет, мир всегда такой приятный, давайте сделаем немного более сложный запрос
запись запросаusers
Найдите все списки пользователей и верните необнуляемый массив длиной 0. Если в массиве есть элементы, они должны быть типа User, другая запись запросаuser
Принимает строку, находит пользователя с идентификатором этой строки и возвращает поле типа User, допускающее значение NULL.
# src/schema.graphql
# Query 入口
type Query {
user(id: String): User
users: [User]!
}
type User {
id: ID!
name: String!
age: Int
email: String!
}
Добавьте соответствующий Resolver
// src/resolvers/index.js
import Db from '../db'
export default {
Query: {
user: (parent, { id }) => Db.user({ id }),
users: (parent, args) => Db.users({})
}
}
два метода здесьDb.user
,Db.users
Это функции, которые ищут соответствующие данные и возвращают обещание.Если обещание разрешено, данные, переданные для разрешения, будут возвращены в качестве результата.
Затем выполните один запрос, чтобы найти всю необходимую информацию.
# 请求
query {
user(id: "2") {
id
name
email
age
}
users {
id
name
}
}
# 返回值
{
"data": {
"user": {
"id": "2",
"name": "李四",
"email": "mmmmm@qq.com",
"age": 18
},
"users": [{
"id": "1",
"name": "张三"
},{
"id": "2",
"name": "李四"
}]
}
}
Обратите внимание, что возвращаемый массив хочет получить толькоid
,name
Эти два поля, чтобы GraphQL не возвращал избыточных данных, как насчет этого, разве это не очень важно?
3.3 Mutation
Если вы знаете, как запрашивать данные, вам также нужно знать, как добавлять, удалять и изменять.В конце концов, это обязательно для CRUD-инженеров, но здесь представлены только более сложные модификации.Вы можете посмотреть на два других метода.Githubначальство.
# src/schema.graphql
# Mutation 入口
type Mutation {
updateUser(id: ID!, name: String, email: String, age: Int): User!
}
type User {
id: ID!
name: String!
age: Int
email: String!
}
Точно так же Mutation также нуждается в Resolver для обработки запросов.
// src/resolvers/index.js
import Db from '../db'
export default {
Mutation: {
updateUser: (parent, { id, name, email, age }) => Db.user({ id })
.then(existUser => {
if (!existUser)
throw new Error('没有这个id的人')
return existUser
})
.then(() => Db.updateUser({ id, name, email, age }))
}
}
Запись мутации updateUser сначала выполняет пользовательский запрос после получения параметров, если он не найден, будет выдана ошибка, и ошибка будет возвращена пользователю в виде сообщения об ошибке.Db.updateUser
Эта функция также возвращает Promise, но возвращает измененную информацию
# 请求
mutation UpdataUser ($id: ID!, $name: String!, $email: String!, $age: Int) {
updateUser(id: $id, name: $name, email: $email, age: $age) {
id
name
age
}
}
# 参数
{"id": "2", "name": "王五", "email": "xxxx@qq.com", "age": 19}
# 返回值
{
"data": {
"updateUser": {
"id": "2",
"name": "王五",
"age": 19
}
}
}
Это завершает изменение данных, получает измененные данные и дает нужные поля.
3.4 Subscription
Еще один интересный аспект GraphQL заключается в том, что он может подписываться на данные.После того, как клиент инициирует запрос на подписку, если серверная часть обнаружит изменения данных, она может передать информацию в реальном времени во внешний интерфейс.Давайте посмотрим.
Как обычно, определите запись Subscription в схеме.
# src/schema.graphql
# Subscription 入口
type Subscription {
subsUser(id: ID!): User
}
type User {
id: ID!
name: String!
age: Int
email: String!
}
Дополните его своим Resolver
// src/resolvers/index.js
import Db from '../db'
const { PubSub, withFilter } = require('apollo-server')
const pubsub = new PubSub()
const USER_UPDATE_CHANNEL = 'USER_UPDATE'
export default {
Mutation: {
updateUser: (parent, { id, name, email, age }) => Db.user({ id })
.then(existUser => {
if (!existUser)
throw new Error('没有这个id的人')
return existUser
})
.then(() => Db.updateUser({ id, name, email, age }))
.then(user => {
pubsub.publish(USER_UPDATE_CHANNEL, { subsUser: user })
return user
})
},
Subscription: {
subsUser: {
subscribe: withFilter(
(parent, { id }) => pubsub.asyncIterator(USER_UPDATE_CHANNEL),
(payload, variables) => payload.subsUser.id === variables.id
),
resolve: (payload, variables) => {
console.log('🚢 接收到数据: ', payload)
}
}
}
}
здесьpubsub
Это класс, отвечающий за подписку и публикацию в apollo-server.Он предоставляет асинхронный итератор при приеме подписки и публикует полезные данные во внешнем интерфейсе, когда сервер чувствует необходимость публикации и подписки.withFilter
Эта функция предназначена для фильтрации нежелательных сообщений о подписке.Фильтр подписки.
Сначала оформляем запрос на подписку
# 请求
subscription subsUser($id: ID!) {
subsUser(id: $id) {
id
name
age
email
}
}
# 参数
{ "id": "2" }
Мы используем операцию обновления данных только сейчас, чтобы сделать изменение данных, а затем мы получим его и распечатаем.pubsub.publish
Опубликованные полезные данные, завершающие подписку на данные.
В графпаке передача данных реализована на основе вебсокета, вы можете открыть Chrome DevTools, чтобы посмотреть во время общения.
4. Резюме
Текущие интерфейсные и серверные структуры примерно таковы. Серверная часть подключается к базе данных через уровень DAO для обеспечения постоянства данных, обслуживая уровень службы, который обрабатывает бизнес-логику, а уровень контроллера принимает запросы API для вызова уровня службы для обработки и возврата; он состоит из вложения компонентов, каждый компонент поддерживает свое собственное состояние на уровне компонентов, а некоторые слегка сложные приложения также используют инструменты централизованного управления состоянием, такие как Vuex, Redux, Mobx и т. д. Front-end и back-end взаимодействуют только через API, что также является основой для разделения front-end и back-end разработки.
Если используется GraphQL, серверная часть больше не будет создавать API, но будет поддерживать уровень контроллера в качестве преобразователя и согласовывать схему с интерфейсной частью.Эта схема будет использоваться для создания документов интерфейса, а интерфейсная часть будет напрямую использовать схему или сгенерированные интерфейсные документы для обработки желаемого запроса.
После нескольких лет заполнения дыр первоклассными разработчиками уже есть несколько хороших.набор инструментовМожет использоваться в разработке и производстве, многие языки также обеспечивают поддержку GraphQL, например, JavaScript/Nodejs, Java, PHP, Ruby, Python, Go, C# и так далее.
Некоторые известные компании, такие как Twitter, IBM, Coursera, Airbnb, Facebook, Github, Ctrip и т. д., преобразовали свои внутренние или внешние API из RESTful в стиль GraphQL, особенно Github, чей внешний API v4 использует только GraphQL. согласно сБольшой парень, работающий в ТвиттереГоворят, что многие компании первого и второго уровня в Силиконовой долине пытаются найти способ перейти на GraphQL, но также говорят, что GraphQL все еще нужно время для разработки, потому что его использование в производственной среде требует большого рефакторинга в передней и задней части, что, несомненно, требует продвижения и решимости на высоком уровне.
так какЧто сказала Ю Юси, почему два-три года назад GraphQL не получил широкого распространения, могут быть следующие две причины:
- Если разрешение поля GraphQL написано наивным образом, каждое поле будет запускать запрос непосредственно к базе данных, что будет генерировать большое количество избыточных запросов.Хотя количество запросов на сетевом уровне оптимизировано, запрос к базе данных может стать узким местом в производительности. Есть много возможностей для оптимизации, но это не так просто сделать. У самих FB этой проблемы нет, потому что их внутренний слой базы данных также абстрагирован, и тем, кто пишет интерфейсы GraphQL, не нужно беспокоиться об оптимизации запросов.
- Преимущества GraphQL в основном заключаются в эффективности разработки внешнего интерфейса, но реализация требует полного сотрудничества сервера. Если это небольшая компания или вся компания с полным стеком, это может быть возможно, но во многих командах с четким разделением труда между фронтендом и бэкендом все равно будут различные сопротивления совместной работе для продвижения GraphQL.
Это можно примерно обобщить как причины узкого места в производительности и разделения труда между командами.Я надеюсь, что с развитием сообщества и улучшением инфраструктуры будет постепенно предлагаться комплексное решение, так что фронт- конечные и бэкенд-разработчики могут использовать этот инструмент как можно скорее.
Большинство сообщений в Интернете имеют разную глубину и даже некоторые несоответствия. Следующие статьи являются кратким изложением процесса обучения. Если вы найдете какие-либо ошибки, пожалуйста, оставьте сообщение, чтобы указать ~
Ссылаться на:
PS: Всех приглашаю обратить внимание на мой публичный аккаунт [Front End Afternoon Tea], давайте работать вместе~
Кроме того, вы можете присоединиться к группе WeChat «Front-end Afternoon Tea Exchange Group», нажмите и удерживайте, чтобы определить QR-код ниже, чтобы добавить меня в друзья, обратите вниманиеДобавить группу, я заберу тебя в группу~