漫谈 GraphQL
本篇文章不会去过多的讨论技术细节,只是从核心概念上去说明 GraphQL 解决了什么问题,是如何怎么做到的
GraphQL 设计思路
GraphQL 是什么?
现在只需要知道,对于前端来说,它是一种资源请求方式,可以用来代替 RESTful 风格的 API 请求。
那为什么要去代替 RESTful 的 API 请求呢?因为遇到了一些问题
- 数据获取
- 数据校验
数据获取
前端的多个功能可能会依赖一份数据源的不同部分
- 后端需要频繁的修改字段获取逻辑
- 不管前端功能中实际用到了几个字段,接口都会返回所有的字段
- 对于字段很多的接口,网络传输的效率会有很大差别

按需获取字段
把需要的字段传进去

获取不同表的字段
把表名传进去

同时查询不同的表
重构传输结构

不同的数据源 & 数据处理
数据源:数据库、redis、ES、RPC 等等
数据处理:多数情况从数据源拿到的数据,还需要进行逻辑处理才会返回

嵌套查询 / 子查询
子查询:先执行父级的查询,再用父级的信息查找子级

重构传输结构

封装、分层

数据校验
拿到的数据和 API 文档不一致
- 文档写的是 number,返回的是 string
- 文档写的不会为空,返回了空
- 99% 不为空,1% 为空
可不可以如果返回的和需要的不一致,就直接报错,接口 500,这样可以更快的定位问题(更好的甩锅)

问题:
- 数据类型是数据源决定的,前端是决定不了的,所以不应该前端去写(写了反而再坑后端)
类型定义

总结

GraphQL 官方设计
GraphQL | A query language for your API

类型定义
内置根类型

| Query 类型定义 | Query 查询 |
|---|---|
![]() | ![]() |
字段解析
从 resolves 中找到对应 type 的各字段解析方法,如果字段的类型不是标量类型(基础类型),就递归对 type 进行解析
内置标量类型:Int、Float、String、Boolean、ID

N + 1 问题
考虑上述代码示例,如果 1 条回答中,有 20 条评论,各 fetch 方法分别会被调用多少次?
fetchAnswer: 1
fetchReviews: 1
fetchUser: 21如果我们查询的是 1 个回答列表,列表中有 20 条回答,每个回答有 20 条评论,上述方法有会被调用多少次?
fetchAnswer: 1
fetchReviews: 20
fetchUser: 420问题
- 过多的 IO
- 重复请求
遇事不决加抽象

简单实现
const arr = new Set()
let flag = true
const fetchUser = (user_id) => {
arr.add(user_id)
if (flag) {
flag = false
setTimeout(() => {
batch_fetch_user([...arr])
arr.clear()
flag = true
})
}
}封装
class Loader {
constructor(callback) {
this.arr = new Set()
this.flag = true
this.callback = callback
}
load(key) {
this.arr.add(key)
if (this.flag) {
this.flag = false
setTimeout(() => {
this.callback([...this.arr])
this.arr.clear()
this.flag = true
})
}
}
}DataLoader
const DataLoader = require("dataloader")
const userLoader = new DataLoader((ids) => {
console.log(ids)
// `SELECT * FROM user WHERE id IN (${ids.join(',')})`
// rpc.client.fetchUsers(ids)
return Promise.resolve(["123"])
})
Array(20)
.fill(1)
.map((_, i) => userLoader.load(i))
Array(20)
.fill(1)
.map((_, i) => userLoader.load(0))总结
- 解析字段时都应该使用 dataloader
- 要求后端提供批量查询的 RPC 方法
Code First Schema
类型语言的问题
重复的写 schema 和 类型(结构)定义,并且两者又是很像的
GraphQL Schema
type Answer {
id: Int
content: String
}Go
type Answer struct {
id int
content string
}Rust
struct Answer {
id: i32
content: string
}TS
class Answer {
id: Number
content: string
}Schema First
先写 schema,生成类型(结构)
Code First
先写类型(结构),生成 schema
调用式

结构式

JS
| lib | star | modal |
|---|---|---|
| graphql-js | 19.7k | 两种都支持:code first(调用式) |
| apollo-server | 13.5k | schema first |
| type-graphql | 7.9k | code first(结构式) |
| graphql-yoga | 7.8k | schema first |
| nestjs | 60.4k / 1.3k | 两种都支持:code first(结构式) |
Go
| lib | star | modal |
|---|---|---|
| graphql-go | 9.5k | code first(调用式) |
| gqlgen | 9.3k | schema first |
Rust
| lib | star | modal |
|---|---|---|
| juniper | 5.3k | code first(结构式) |
| async-graphql | 3k | code first(结构式) |
觉得有帮助?给个 Star 支持一下吧!
Star on GitHub



