GraphQL 一种更灵活的 API 解决方案
简单介绍
GraphQL 是一种用于 API 的查询语言和运行时环境,由 Facebook 开发并于2015年公开发布。它旨在提供一个更高效、强大且灵活的替代方案,用于客户端和服务器之间的数据交互。与传统的 RESTful API 不同,精确的数据查询,允许仅获取所需的数据等都是 GraphQL 的优势。
GraphQL 的优势
GraphQL 的优势主要在于精确的数据查询,允许仅获取所需的数据,从而节省带宽和资源,这将通过一个例子来理解。
GraphQL 服务是通过定义类型和字段,为每种类型上的每个字段提供函数来创建的。查询用户的 GraphQL 服务可能长这样:
type Query {
me: User
}
type User {
id: ID!
name: String!
}
GraphQL 服务在运行时主要由两部分组成,Query Language 和 Engine,后端定义了类型和字段,客户端一样需要通过 HTTP 或者 WebSocket 发送请求,然后 GraphQL Engine 会根据请求的查询和类型定义来返回数据。
例如查询 “me”:
{
me {
name
}
}
后端返回:
{
"me": {
"name": "Luke Skywalker"
}
}
查询与返回结果具有完全相同的字段,这便是 GraphQL 的优势之一,你总能获取你期望的结果,而后端确切的知道客户端请求的字段。
在 GraphQl 查询中,可以通过给字段添加参数来完美替代多次 API 获取请求。
例如,假设我们想要获取 Luke Skywalker 的朋友,我们可以使用如下查询:
{
people(name: "Luke Skywalker") {
id
friends(first: 1) {
name
}
}
}
返回结果
{
"people": {
"id": "1",
"friends": [
{
"name": "Han Solo"
}
]
}
}
类型系统(Type System)
查询和变更类型(Query and Mutation)
在 schema 中有两个特殊类型: query 和 mutation。每个 GraphQL 服务都有一个 query 类型,可能存在一个 mutation 类型。它们定义了每个查询的入口。
标量类型(Scalar Types)
GraphQL 自带的默认标量类型有:
- Int:32位有符号整数
- Float:双精度浮点型
- String:UTF-8 字符串
- Boolean:布尔值
- ID:表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。
自定义标量:可以通过定义标量,序列化和反序列化来自定义类型。
scalar Date
枚举 (Enumeration Types)
可以通过枚举将值限制在可选值集合内。
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
这意味着当我们在 schema 任意位置使用 Episode 时,它的返回只能是 NEWHOPE、EMPIRE 或 JEDI 之一。
列表和非空 (Lists and Non-Null)
列表和非空类型,可以通过给类型添加额外的类型修饰符来影响值的验证。
type Character {
name: String!
appearsIn: [Episode]!
}
其中 !
表示非空,意味着对于这个字段总会返回一个非空值,如果返回了空值,GraphQL 会返回一个错误。[]
类似,表示一个类型为 List。可以通过相互组合来达到某种效果:field: [String!]
表示 field 是非空数组, field: [String]!
表示 field 一定会返回数组,但可能是空数组。
接口 (interface)
接口是一个抽象类型,它包含一些字段,而对象类型必须包含这些字段才算实现了接口,并且可以包含额外的字段。
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
# 必须实现以上字段
starships: [Starship]
totalCredits: Int
}
联合 (union)
联合类型和接口十分相似,但他并不会要求类型之间必须共同实现字段。
union SearchResult = Human | Droid | Starship
如果使用联合类型,查询时需要使用内联片段来指定返回类型。
{
search(text: "an") {
__typename
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}
{
"data": {
"search": [
{
"__typename": "Human",
"name": "Han Solo",
"height": 1.8
},
{
"__typename": "Human",
"name": "Leia Organa",
"height": 1.5
},
{
"__typename": "Starship",
"name": "TIE Advanced x1",
"length": 9.2
}
]
}
}
输入类型 (input)
在进行变更操作的时候(mutation),有时候参数是一个复杂对象,这时候就需要使用到关键字 input 创建输入类型,它和常规对象的区别只在于使用关键字为 input 而非 type
input ReviewInput {
starts: Int!
commentary: String
}
字段修饰,弃用字段 (@deprecated)
在字段后面添加 @deprecated 可以标记字段为弃用,GraphQL 会返回一个警告,但不影响继续使用这个字段,这在接口迭代时非常有用。并且可以添加 reason 字段来描述弃用原因。
type Character {
name: String @deprecated(reason: "Use `hero` instead")
}
思考
- 将业务建模成图:通过 GraphQL,你会把自己的业务领域通过定义 schema 建模成一张图;在你的 schema 里,你定义不同类型的节点以及它们之间是如何连接的。在客户端这边,这创建了一种类似于面向对象编程的模式:引用其他类型的类型。
- 规范的命名格式,命名是构建接口过程中极其重要且困难的部分。因为 GraphQL 是图的模型,所以在命名时尽量使用节点和关系直观的名称。
type Query {
# 获取账户的前二十条草稿邮件
# 查询字段应该直接使用名词,不要使用动词 + 名词(例如:getAccounts / getAccountById 等)
account(id: ID!) {
drafts(first: Int): Email
unreadEmailCount: Int
}
}
type Email {
id: ID!
subject: String!
}
- 不是试图使用一个模型来构建整个项目。
- URI 和路由:HTTP 通常与 REST 相关联, REST 使用“资源”为核心。GraphQL 的模型是实体图。所以 GraphQL 的实体无法通过 URL 识别。服务器在单个 URL/入口端点上运行,所有的 GraphQL 请求都应被导向此入口。
- 分页:在请求中,经常会遇到需要返回列表数据的情况。对于列表而言,GraphQL 在处理切片上有自己的规范:基于游标的分页,可以通过一个例子来快速掌握:
{
hero {
name
# first 限制返回条目,而after 是游标(游标一般不可读)用于标识偏移
friendsConnection(first:2 after:"Y3Vyc29yMQ==") {
totalCount
# 引入新的间接层 edges ,在 friends 的基础上返回当前游标
edges {
node {
name
}
cursor
}
pageInfo {
endCursor
hasNextPage
startCursor
hasPreviousPage
}
}
}
}
参考
你可以在这里找到参考资料