All of the data you need, in one request
GraphQL is an open spec for a flexible API layer.
Ask exactly what you want.
GraphQL是一个用于API的查询语言。GraphQL并没有和特定数据库或者存储引擎绑定,而是依靠现有的代码和数据支撑。和RESTful不同的是,GraphQL会在一个请求中获取所有想要的数据,比如我们想要从服务器获取id=1
的书籍name
信息和id=2
的文章的title
信息,则对于GraphQL请求,我们只需按照下方语法来发送请求即可获得想要的信息。
query{
book(id:"1") {
name
},
article(id:"2") {
title
}
}
但是,对于RESTful API,我们就不得不按照类似于host:port/api/book/1/
和host:port/api/article/2/
的url
来向服务器发送两次请求,然后对返回的数据进行筛选得到name
和title
字段。这只是GraphQL和RESTful的其中一个区别,有关两者的比较,详见传送门。
基于GraphQL的服务构建主要有四个部分:数据定义(schema)、查询(query)、更改(Mutation)、数据解析(Resolver)
gqlgen is a schema-first library — before writing code, you describe your API using the GraphQL Schema Definition Language. This usually goes into a file called
schema.graphql
。
首先,我们需要定义Schema(模型),在此文件中,我们需要定要定义各种数据类型。Schema 明确了服务端有哪些字段(用户自定义类型)可以用,每个字段的类型和子字段。每次查询时,服务器就会根据 Schema 验证并执行查询。
在Schema文件中,有4个比较特殊的关键字:
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
Type
关键字是用来定义抽象数据类型,类似于golang中的Type但是并不相同。在每一个自定义数据类型中,可以有多个Field
(字段),每个Field可以再次指向某个Type
。
Scalar
是解析到单个标量对象的类型,无法再进行次级选择(次级选择的含义在阅读GraphQL查询语法之后会有所了解)。GraphQL中包含的标量有String
,Int
,Float
,Boolean
,Enum
,ID
。
The
ID
scalar type represents a unique identifier, often used to refetch an object or as key for a cache.
# 定义性别标量
enum Gender {
MALE
FEMALE
}
与我们在其他语言中定义对象类似:下方Person这个自定义数据类型中包括了id
、name
等字段。
type People {
name: String
birth_year: String
gender: String
}
上述People类型中只有标量字段,我们同样可以使用自定义数据类型字段,例如,我们定义了Film数据类型,每一部Film都有一个director字段:
type Film {
name: string
director: People
...
}
接口是一个抽象类型,相信学习过go和java的读者都不陌生,下面直接看定义:
type Human { # 实现Human的Type必须有这两个字段
age: Int
name: String
}
type Programmer implements Human {
age: Int
name: String
Hair: Int
field: String
}
type Student implements Human {
age: Int
name: String
id: ID
major: String
}
对于上面People类型中的name字段,假如我们想要让其不为空,则可以在数据类型后面添加感叹号!
,如果我们要新增字段参演电影的列表films,则可以使用[]
。
type People {
name: String!
birth_year: String
gender: String
films: [Film]
}
同样,我们可以对列表进行非空限制:
myField: [String!]
# 表示数组本身可以为空,但是其不能有任何控制成员
myField: null # 有效
myField: [] # 有效
myField: ['a', 'b'] # 有效
myField: ['a', null, 'b'] # 错误
myField: [String]!
# 这表示数组本身不能为空,但是其可以包含空值成员:
myField: null // 错误
myField: [] // 有效
myField: ['a', 'b'] // 有效
myField: ['a', null, 'b'] // 有效
联合类型和接口十分相似,但是它并不指定类型之间的任何共同字段。
union SearchResult = Person | Film
任何返回一个 SearchResult
类型的地方,都可能得到一个 Person 或者 Film。注意,联合类型的成员需要是具体对象类型;不能使用接口或者其他联合类型来创造一个联合类型。
输入常常用于变更(mutation)中,类似于post请求来新建对象。
input PersonInput {
name: String
birth_year: String
gender: String
films: [Film]
}
在schema中,定义查询方法如下:
# 定义people查询方法,参数为必填字段id,返回数据类型为People
type Query {
people(id: ID!): People
}
下面介绍一下如何查询:
在下属查询方法中,people()为定义的查询方法
如果我们先要再一次请求中查询两个people,则会出现下方的错误:
对于上述情况,我们可以使用别名的方式来进行查询:
在上面的people5和people1的查询中,我们发现,两者都查询了name,此时只有一个字段还好,如果相同字段过多时,那应该怎么办呢?这种情况下我们便可以使用fragment:
片段的概念经常用于将复杂的应用数据需求分割成小块,特别是你要将大量不同片段的 UI 组件组合成一个初始数据获取的时候。
上述所有查询,我们都使用了query关键字作为查询标识,虽然可以省略,但依然推荐这么加上。实际上,我们还可以为我们的查询定义名称,这对我们在开发过程中寻找可能存在的漏洞提供帮助。例如:定义一个名称为filmQuery的查询操作
query filmQuery{
film(id:"1"){
title
}
}
后续将要讲述的mutation操作也可以定义名称。
使用变量的步骤:
$variableName
替代查询中的静态值。$variableName
为查询接受的变量之一。variableName: value
通过传输专用(通常是 JSON)的分离的变量字典中。使用变量可以很方便的在客户端构造查询语法,客户端可以构造一个复选框,下拉菜单等方式来获取动态参数,然后将动态参数提取到查询之外,作为分离的字典传进去。而不用构建一个全新的查询。(为了安全起见,我们不能使用用户提供的值来进行字符串插值构建查询)
我们也可以使用默认变量,定义如下:
query peopleQuery($id: ID = "5"){
...
}
上述介绍的全部都是查询操作,GraphQL也为我们提供了mutation变更操作,用于修改数据。
# 参数为Episode(Enum)以及一个Input类型的输入数据,返回类型为Review
type Mutation {
createReview(episode: Episode!, review: ReviewInput!): Review
}
变更和查询一样都可以使用变量以及片段等:
查询字段时,是并行执行,而变更字段时,是线性执行,一个接着一个。
订阅用于real-time
实时请求。具体用法可以自行谷歌。
当用户请求发送到服务器时,服务器如何进行相应并返回所需数据呢?下面介绍一下GraphQL的响应过程,以query查询为例:
query
,查询方法为people
。people
解析(Resolver)函数,在此解析函数中,我们会调用其他函数(此函数通常需要自己手动实现)从数据库查询id
为35
的people对象并返回,第一层解析结束。name
,和films
,
上述过程中大部分函数其实并不需要手动实现,这些操作对于我们来说相当于黑盒状态,我们接下来会介绍几个常用的GraphQL生成工具。
关于GraphQL的相关内容就介绍到这里,如果想有进一步的了解,可以前往GraphQL官网进一步学习。
以go语言为例,graphql构建工具有:
其中gqlgen支持语法最多,我的另一篇文章中介绍了如何使用gqlgen构建graphql服务。