GraphQL是什么?
GraphQL来自Facebook,在2015年开源。它是一种API查询语言。回想一下SQL,是不是有点惊人的相似?它们都是query language。SQL是Structured Query Language
,GraphQL很显然,是Graph Query Language
。这时候,你们可能会问,为什么叫graph?
MySQL是SQL的实现,Apollo, Relay也是GraphQL规范的实现
SQL的数据源是数据库,GraphQL的数据源可以是restful API,各种服务/微服务,或者数据库
为什么叫graph?
在GraphQL的世界里,万物皆为图,可以把你的业务模型建模为图。由于图的结构更接近于自然世界,相比关系型数据库,在设计图数据库时,会省去一个图结构向关系型结构的转化工作。
GraphQL有什么好处?
- 前端灵活性。可以按需取字段,也可以对接口聚合,一次拿到所有的数据
- 后端复杂度降低。不再需要维护接口的版本号了,减少冗余的业务代码(很可能会新增与现有API重复度较高的API,造成后端业务代码的冗余)
- 强类型。完备的类型校验机制,提供了更健壮的接口
- 可读性强,代码即文档
废话不多说,先来了解下GraphQL如何定义接口的,看下他的接口长什么样。
数据准备
首先准备一个简单的用户列表数据。注意:数据可来源于数据库、上游服务端/微服务端等,GraphQL本身不是数据库。
[
{ id: 1, key: 1, name: "John Doe", email: "john@gmail.com", age: 22 },
{ id: 2, key: 2, name: "Jane Doe", email: "jane@gmail.com", age: 23 }
]
复制代码
Schema
Schema在GraphQL里相当于传统意义上的接口文档,可在schema中定义查询的name,入参和出参(返回的字段)。
每一个Schema都有一个Root Query
和Root Mutation
。
- Query:定义查询动作
- Mutation: 定义增删改的动作
基于刚刚准备的用户列表,我们来看下怎么编写和定义针对用户的接口。如下所示,会发现schema
定义方式和typescript
很像。
// schema.graphql
type Query {
// 用户列表
users: [User]!
// 单个用户详情
user (id: ID!): User!
}
type Mutation {
// 添加用户
addUser (id: ID!, name: String!, email: String!, age: Int): User
// 更新用户
updateUser (id: ID!, name: String, email: String, age: Int): User
// 删除用户
deleteUser(id: ID!): User!
}
type User {
id: ID!
key: Int!
name: String!
email: String!
age: Int!
}
复制代码
type
从上面可以看出,类型type是schema的重要组成。强类型是graphql的重要特性之一。类型分为两类:
- 标量类型(基础类型):Int(整型), Float(浮点型), String(字符串), Boolean(布尔型)和ID(唯一标识符类型)
枚举类型enum,是特殊的标量类型,可枚举的的可选值的集合
- 对象类型:跟业务相关的类型,比如User类型。
type User {
id: ID!
key: Int!
name: String!
email: String!
age: Int!
}
复制代码
注意:基础类型是叶子节点。定义对象类型时候,直到全部为基础类型
传递参数
// 参数id
type Query {
user (id: ID!): User!
}
// 参数id, name, email, age
type Mutation {
addUser (id: ID!, name: String!, email: String!, age: Int): User
}
复制代码
感叹号的含义
- 表示从服务器获取这个字段,返回的是一个非空值
// 返回一个非空的User类型数组
type Query {
users: [User]!
}
复制代码
- 定义一个字段的参数,传递的参数不能是空值
// 传递的id为非空ID
type Query {
user (id: ID!): User!
}
复制代码
复杂度分析
一个graphql的请求可能会给服务器带来巨大的压力,数据库操作太多的话,可能会导致DDoS攻击。复杂度分析通过@complexity
实现
type Query {
me: User @complexity(value: 10)
}
复制代码
废弃字段
可以标记某一个字段为废弃,并给出废弃原因。这样,在版本迭代时,就可以友好的提示到旧版本的使用者,促使其升级到最新的接口。废弃字段通过加@deprecated
实现
type User {
avatar: String @deprecated(reason: "Moved to UserAvatar.")
}
复制代码
已经了解了接口的定义,那各个接口如何去取数据和处理数据?graphql通过resolver来实现这点。
resolver
接下来,我们需要为Root Query(根查询)和Root Mutation(根变更)里面的每一个字段提供一个resolver的函数。
import {users} from '../db';
const resolvers = {
Query: {
users: (parent, args) => {
return users
},
user: (parent, {id}) => {
return users.find(item => item.id == id)
}
},
Mutation: {
updateUser: (parent, {id, name, age}) => {
let user = users.find(item => item.id == id)
user.name = name
user.age = age
return user
}
}
}
export default resolvers
复制代码
接下来,我们通过graphpack来搭建一个简单的graphql server
Graphpack实战
Graphpack可以创建一个简易的零配置的graphql server
Graphpack 是集成了 Webpack + Express + Prisma + Babel + Apollo-server + Websocket 的支持热更新的零配置 GraphQL 服务环境
首先,新建一个空项目,yarn init
之后,安装graphpack
yarn add graphpack
复制代码
配置graphpack启动和打包脚本
// package.json
"scripts": {
"dev": "graphpack",
"build": "graphpack build"
}
复制代码
新建目录src,目录结构如下
- db/index中存放用户的mock数据
export let users = [
{ id: 1, key: 1, name: "John Doe", email: "john@gmail.com", age: 22 },
{ id: 2, key: 2, name: "Jane Doe", email: "jane@gmail.com", age: 23 }
];
复制代码
- schema.graphql中定义schema
type Query {
users: [User]!
user (id: ID!): User!
}
type Mutation {
addUser (id: ID!, name: String!, email: String!, age: Int): User
updateUser (id: ID!, name: String, email: String, age: Int): User
deleteUser(id: ID!): User!
}
type User {
id: ID!
key: Int!
name: String!
email: String!
age: Int!
}
复制代码
- resolvers/index中定义各解析函数
import {users} from '../db';
const resolvers = {
Query: {
users: (parent, args, context, info) => {
return users
},
user: (parent, {id}, context, info) => {
return users.find(item => item.id == id)
}
},
Mutation: {
addUser: (parent, { id, name, email, age }, context, info) => {
const user = { id, name, email, age }
users.push(user)
return user
},
updateUser: (parent, { id, name, email, age }, context, info) => {
const user = users.find(item => item.id == id)
user.name = name
user.email = email
user.age = age
return user
},
deleteUser: (parent, { id }, context, info) => {
const userIndex = users.findIndex(user => user.id === id);
if (userIndex === -1) throw new Error("User not found.");
const deletedUsers = users.splice(userIndex, 1);
return deletedUsers[0];
}
}
}
export default resolvers;
复制代码
yarn dev
启动server,打开http://localhost:4000
。在这个界面中,我们可以测试各接口,有点类似于postman。
先测试一下用户列表users和单个用户详情user的查询
query {
users {
key
name
age
email
}
user (id: 1) {
name
age
}
}
复制代码
再来测试一下更新用户mutation操作。更新id=1的用户信息
mutation {
updateUser (id: 1, name: "lily", age: 20, email: "12@qq.com") {
id
name
age
email
}
}
复制代码
更新完后,切换到users列表再刷新,发现数据已经变成最新的用户信息
其他的mutation变更操作类似,感兴趣的同学可以一一试试。
最后
graphql server端已经搭建好,在react客户端中怎么去请求?下一章节待续…