GraphQL是由Facebook开发的数据查询语言,旨在使API更快、更灵活、更开发者友好。
与REST API不同,它允许客户端定义所需数据的结构,并通过单个请求接收所需信息。它还使用类型系统来明确定义请求和响应的数据结构,提高代码稳定性。
"你可以只获取你想要的,只获取你需要的量" 是其最显著的特点。
REST API 🆚 GraphQL
假设有一个包含国家信息的REST API。要检索国家列表,让我们调用/countries
端点。
/countries
// GET /countries 响应
[
{
"code": "AD",
"name": "Andorra",
"capital": "Andorra la Vella",
"region": "Europe"
},
{
"code": "AE",
"name": "United Arab Emirates",
"capital": "Abu Dhabi",
"region": "Asia"
},
{
"code": "AF",
"name": "Afghanistan",
"capital": "Kabul",
"region": "Asia"
},
// ... 超过200个国家数据
]
我们已经找到了一个国家的唯一ID代码。现在,如果我们想了解有关该国家的更详细信息,我们需要调用另一个API。
/countires/KR
// GET /countries/KR 响应
{
"code": "KR",
"name": "South Korea",
"native": "대한민국",
"phone": "82",
"capital": "Seoul",
"currency": "KRW",
"languages": ["ko"],
"emoji": "🇰🇷",
"region": "Asia",
"subregion": "Eastern Asia",
"states": [
{ "name": "Seoul", "code": "11" },
{ "name": "Busan", "code": "26" },
{ "name": "Incheon", "code": "28" },
// ... 更多地区
]
}
这种方法有什么问题?
- 过度获取问题:如果我们只需要显示国家名称和首都,我们不需要获取其他信息。然而,当我们调用API时,我们会收到所有国家的所有数据。随着数据量的增加,流量也会增加,这可能导致性能下降。
- 获取不足问题:要显示详细的国家信息,需要两次API调用。因此,要同时显示国家及其详细信息,需要多次API调用。
通过GraphQL解决
上面列出的REST API问题可以通过GraphQL解决,如下所示:
- 所有请求都可以通过单个端点处理。这简化了管理和维护。
- 客户端可以只请求他们需要的数据。客户端可以指定所需数据的结构,减少不必要的数据传输,提高网络效率。
🆚 /countries
query {
countries {
name
capital
emoji
}
}
响应
{
"data": {
"countries": [
{
"name": "Andorra",
"capital": "Andorra la Vella",
"emoji": "🇦🇩"
},
{
"name": "United Arab Emirates",
"capital": "Abu Dhabi",
"emoji": "🇦🇪"
},
{
"name": "Afghanistan",
"capital": "Kabul",
"emoji": "🇦🇫"
},
// ... 更多国家
]
}
}
🆚 /countries/KR
query {
country(code: "KR") {
name
native
capital
emoji
currency
languages {
name
native
}
continent {
name
}
}
}
响应
{
"data": {
"country": {
"name": "South Korea",
"native": "대한민국",
"capital": "Seoul",
"emoji": "🇰🇷",
"currency": "KRW",
"languages": [
{
"name": "Korean",
"native": "한국어"
}
],
"continent": {
"name": "Asia"
},
}
}
}
我们只用一个请求就获得了所有需要的信息!🫢
GraphQL的优势
1. 单一端点
GraphQL的特点之一是它只使用一个端点。在REST API中,每个资源都需要不同的端点,如/countries
、/countries/KR
、/countries/KR/borders
、/languages
,但GraphQL通常只使用一个URL,通常是/graphql
。
- 简化前端开发:开发者只需要知道一个端点,而不是记住多个端点或查找文档。
- 增强安全性:只需保护一个入口点,允许集中认证和授权逻辑。
2. 只请求所需的数据(解决过度获取)
使用GraphQL,客户端可以精确请求他们需要的数据。这可以解决过度获取问题。
- 减少网络流量:不传输不必要的数据,减少带宽使用量。
- 改善响应时间:通过只处理所需的数据,可以改善服务器响应时间。
3. 通过单个请求获取相关数据(解决获取不足)
使用GraphQL,可以通过单个请求获取多个资源的数据。这可以解决获取不足问题。
- 减少网络请求:所有数据通过一个请求获取,而不是多个API调用。
- 消除瀑布式请求:不需要REST中常见的顺序API调用。
- 简化前端代码:不需要复杂的代码来协调多个API调用。
4. 强大的类型系统
GraphQL基于严格的类型系统。每个字段的类型都被明确定义,大大增强了API的稳定性和可预测性。
- 编译时错误检测:在开发过程中可以检测到类型错误。
- 自动代码生成:可以基于类型定义自动生成客户端代码(例如,TypeScript接口)。
- 增强IDE支持:开发环境支持通过代码自动完成、类型检查等得到增强。
- 明确的契约:在前端和后端之间形成明确的数据契约。
GraphQL的缺点
1. 复杂查询处理负担
GraphQL为客户端提供查询灵活性,但这可能会给服务器端带来负担。
- 复杂查询的性能影响:非常嵌套的查询或请求多个资源的复杂查询可能会给服务器带来沉重的负担。
query NestedQuery {
continents {
countries {
languages {
countries {
languages {
countries {
...
}
}
}
}
}
}
}
- 资源攻击可能性:恶意用户可以故意发送消耗服务器资源的非常复杂的查询。
2. 缓存实现的复杂性
REST API可以基于URL轻松缓存,但GraphQL需要更复杂的缓存策略。
3. 错误处理的差异
- 缺乏HTTP状态码利用:错误包含在响应体中,这可能难以处理。
- 部分失败处理:查询的一部分可能会失败,因此客户端需要处理部分成功/失败场景。
- 错误调试:在复杂查询中出现问题时,调试可能更加困难。
4. 过度灵活性的风险
GraphQL的灵活性可能是一把双刃剑。
- API版本管理混乱:可以轻松添加字段,这可能导致缺乏明确的版本管理。
- 查询复杂性增加:随着时间的推移,客户端查询可能变得越来越复杂。
- 后端逻辑暴露:灵活的查询可能无意中暴露内部系统结构。
- 难以理解API使用模式:各种客户端查询模式可能使识别最重要的数据和字段变得困难。
GraphQL基本类型
GraphQL的强大功能之一是其类型系统。让我们看看有哪些类型可用。
类型 | 描述 |
---|---|
String | UTF-8字符串 |
ID | 基本上是一个String,但表示它作为唯一标识符 |
Int | 有符号32位整数 |
Float | 有符号浮点值 |
Boolean | 真/假 |
!(非空)
表示特定字段不能包含null
。
示例
const typeDefs = gql`
type Supplies {
id: ID!
name: String!
price: Int
}
`
enum (枚举类型)
返回预先指定值之一的类型。
const typeDefs = gql`
enum Role {
developer
designer
planner
}
enum NewOrUsed {
new
used
}
`
[] (数组类型)
表示特定类型是一个数组。
const typeDefs = gql`
type Foods {
ingredients: String[]
}
`
此外,!
的位置可以表示不同的含义。
声明 | users: null | users: [ ] | users: [..., null] |
---|---|---|---|
[String] | ✔ | ✔ | ✔ |
[String!] | ✔ | ✔ | ❌ |
[String]! | ❌ | ✔ | ✔ |
[String!]! | ❌ | ✔ | ❌ |
union类型
当你想将多个已写类型捆绑在一起时使用。
const typeDefs = gql`
union Given = Equipment | Supply
`;
interface类型
用于创建类似对象类型的类型,为implements而编写。如果继承对象中未实现接口中的字段,则会发生错误。
interface Produce {
id: ID!
name: String!
quantity: Int!
price: Int!
}
# OK
type Fruit implements Produce {
id: ID!
name: String!
quantity: Int!
price: Int!
}
# ERROR !!
type Vegetable implements Produce {
id: ID!
name: String!
quantity: Int!
}
input类型
可以指定应该进入查询或变更的参数类型。
input PostPersonInput {
first_name: String!
last_name: String!
}
type Mutation {
postPerson(input: PostPersonInput): People!
}
何时选择GraphQL?
GraphQL是创建更灵活、更高效API的强大工具。虽然REST API在许多情况下仍然是一个好选择,但在具有复杂数据需求和支持各种客户端的情况下,GraphQL可以提供明显的优势。
**然而,请记住GraphQL不是通用解决方案,而是适合特定情况的工具。**重要的是要很好地分析项目需求,并相应地在REST API和GraphQL之间做出选择。
在以下情况下,GraphQL可能是一个好选择:
- 具有复杂数据关系的应用程序
- 像国家信息应用程序一样,多个实体之间有复杂关系
- 具有用户、帖子、评论、点赞等各种关系的社交网络
- 需要支持各种客户端
- 在网络、移动应用、桌面等各种平台上有不同的数据需求
- 当每个客户端需要从同一后端获取定制数据时
- 当快速产品迭代和前端开发速度很重要时
- 当前端需求需要在不改变后端的情况下轻松适应时
- 用户界面和功能经常变化的初创公司或敏捷环境
- 当需要微服务集成时
- 当需要将多个微服务的数据集成到一个一致的API中时
- 实现BFF(Backend For Frontend)模式时
REST仍然适合的情况
在以下情况下,REST API可能更合适:
- 当简单的CRUD操作占主导地位时
- 具有简单数据结构和很少关系的应用程序
- 当数据请求模式一致且可预测时
- 当HTTP缓存很重要时
- 高效HTTP缓存很重要的情况,如公共API
- 当通过CDN缓存是关键性能因素时
- 当文件上传是主要功能时
- 当大文件处理和流式传输是关键需求时
- 当需要最小化团队的学习曲线时
- 当团队熟悉REST,学习新范式的时间有限时
结论
像所有技术一样,GraphQL也伴随着特定的权衡。最终,最重要的是满足用户和业务的需求。无论是GraphQL、REST还是结合方法,让我们选择适合项目特定情况和目标的技术!
参考