为什么要使用GraphQL
GraphQL是由Facebook团队在2015年开源推出的一套用于替代传统的REST API的框架。利用它能够大幅提升开发效率以及应用性能。设想这样一个场景:
你想要获得一个用户的朋友的国家信息,如果你使用的是REST API的话,你可能需要同时调用以下这些接口:
- /users/{userName}/friends: 返回类似
{id: 1, name: 'xxx', friends: [2,3,4] }
的结构 - /friends/{ID}: 返回类似
{id: 2, name: 'xxx', country: 23}
的结构 - /countries/{ID}: 返回类似
{id: 23, name: 'China', flag: 'xxx', language: 'Chinese'}
的信息
先取回以scq000
为用户名的朋友id,然后利用这些返回的用户id再查询详细的信息,最后再拼装成想要的数据结构。你会发现,随着应用复杂度的提升,API的数量以及请求的数量成倍地增加,客户端为了想要获得一个简单的数据信息,要往返多次地调用不同的API。
而GraphQL的出现就是为了解决上述这些问题的,它本质上是一门面向API的查询语言(Query Language for API)。可以根据客户端想要的数据,一次性地将数据取回来。
比如,上面的这个例子就能用下面的Graph 请求来描述:
{
user {
friend {
name
country
}
}
}
然后返回的数据是像这样:
{
"user": {
"friend": {
"name": "scq000",
"country": "China"
}
}
}
同时目前GraphQL也支持各种语言,对于主流的编程语言都有良好地支持,生态环境也搭建地不错。国外的大公司,如facebook, paypal, twitter, github等现在都在项目中大量使用GraphQL。
核心概念
GraphQL中一个核心的概念就是Schema, 它是客户端和服务端之间进行沟通的协议,定义好对应的数据schema后,前后端就可以独立地进行开发,从而提高效率。
一个schema是由query和mutation两部分组成的,如:
schema {
query: Query,
mutation: Mutation
}
其中, query对应的就是请求数据的对象,而mutation主要负责进行副作用,如create, delete, update等操作。
types
一个GraphQL schema中最基本的组件是对象类型,可以用来表示能从服务器获取什么类型的对象。GraphQL内置的标量数据类型(Scalar Types)有以下几种:
-
Int: a signed 32bit 带符号的32位整数
-
Float: 有符号双精度浮点型
-
String: UTF-8编码的字符串
-
Boolean: 布尔型,true和false两个值
-
ID: 唯一标识符,用以重新获取对象或作为缓存的键
还有枚举类型enum:
enum Country {
CHINA
JAPAN
AMERICAN
}
默认情况下每一个内置类型的都可以被设置成null,如果需要确保某个字段不为空,需要使用!
符号:
type Author {
id: ID!
firstName: string,
courses: [String]
}
注意到courses
的类型是[String]
,这表示这个字段是个数组类型。
除了使用内置类型外,还可以自定义类型:
type Query {
author_details: [Author]
}
type Mutation {
addAuthor(firstName: String, lastName: sring): Author
}
Queries
Fields
字段是Query对象上最基本的组成单位,服务端和客户端的结构基本相同:
{
viewer {
name
}
}
Arguments
在GrahQL中,你可以给字段传递参数。这样可以避免重复的API请求,如下所示:
{
followers (last: 3) {
nodes {
id
}
}
}
这表示请求最后3个记录,你还可以使用:
{
author (id: "1000") {
name
age
}
}
这表示请求id为1000的记录。
Alias
由于在请求中不能使用不同的参数来请求相同的字段,因此就需要使用alias来给字段起别名:
{
firstFollowers: followers (first: 3) {
nodes {
id
name
}
}
lastFollowers: followers (last: 3) {
nodes {
id
name
}
}
}
Fragments
片段(Fragment)是可复用的单位,可以让你在多个Query对象中利用同一个Fragments:
{
nodes {
...userInfo
}
}
fragment userInfo on User {
id
bio
}
用fragment
关键字声明一个片段对象,然后再使用的时候直接利用扩展运算符...
就可以了。
Operation name
我们一般在声明查询对象的时候都是用简写形式省略query
关键字和查询名称,不过如果需要减少代码歧义,则需要显式声明:
query ViewerInfo {
viewer {
name
date
}
}
Variables
字段的参数可以是动态的,所以使用变量进行控制:
query ViewerInfo($isOwner: Boolean!) {
viewer {
id
name
start(ownedBy)
}
}
{
isOwner: false
}
Mutations
用来更新数据的(create, update, delete)的副作用。
Query的时候所有的查询是同时执行的,但是Mutation操作是按顺序执行的:
mutation NewStatus($input: ChangeUserStatusINput!) {
changeUserStatus(input: $input) {
clientMutationId
status {
message
}
}
}
生态以及工具
整个GraphQL架构是有客户端和服务端两部分组成中,其中客户端主要负责处理数据请求的状态管理,缓存,分页,错误处理以及schema的校验等工作。而服务端主要和数据库打交道, 有schema 和resolvers, resolver主要处理业务逻辑。
现在比较主流的GraphQL客户端框架有Apollo Client和Relay。
服务端可以使用Apollo Server, express graphql, graphql yoda等。
数据库方面可以使用Prisma, 是替代传统的ORM框架的方案,支持多种数据库。
最后再推荐一些其他有用的工具:
graphql voyager: 图示,模型设计的工具
graphql faker: mock 数据
graphql visual editor: 可视化编辑器
如何使用
最后,我们来写一个实际的demo来应用一下GraphQL:
首先我们要先来定义一下schema对象:
import {
GraphQLSchema,
GraphQLObjectType,
GraphQLInt
} from "graphql";
let counter = 1;
let schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: () => ({
counter: {
type: GraphQLInt,
resolve: () => counter
}
})
}),
mutation: new GraphQLObjectType({
name: "Mutation",
fields: () => ({
incrementCounter: {
type: GraphQLInt,
resolve: () => ++counter
}
})
})
})
这里定义了一个counter
的字段,然后mutation里有一个incrementCounter
的方法,每次执行这个mutation,都会让counter
的值加1。
有了schema后,接着就可以利用express来开启一个叫做/graphql
的接口,并使用已经定义好的schema:
const app = express();
const port = 3000
app.use('/graphql', GraphQLHTTP({
schema
}));
app.listen(port, () => console.log(`Example app listening on port ${port}`))
最后我们就可以直接在浏览器中输入localhost:3000/graphql
使用了