GraphQL vs REST:选哪个?
REST 用了五年,GraphQL 用了两年。对比一下。
REST 的优缺点
优点
- 简单直观
- HTTP 语义明确
- 缓存机制成熟
- 生态完善
缺点
- 过度获取/获取不足
- 接口数量多
- 版本管理麻烦
GraphQL 的优缺点
优点
- 按需获取
- 单一入口
- 类型系统
- 文档自动生成
缺点
- 学习曲线陡
- 缓存复杂
- 安全性需要注意
对比
| 维度 | REST | GraphQL |
|---|---|---|
| 学习成本 | 低 | 高 |
| 灵活性 | 一般 | 高 |
| 缓存 | 简单 | 复杂 |
| 调试 | 简单 | 需要工具 |
| 文档 | 需要额外维护 | 自动生成 |
| 错误处理 | HTTP 状态码 | 自定义 |
场景对比
REST 更适合
| 场景 | 原因 |
|---|---|
| 简单 CRUD | 没必要用 GraphQL |
| 公共 API | REST 更通用 |
| 需要强缓存 | HTTP 缓存机制成熟 |
| 小团队 | 不需要额外学习成本 |
GraphQL 更适合
| 场景 | 原因 |
|---|---|
| 复杂数据关系 | 一次查询获取关联数据 |
| 多端适配 | 不同端需求不同 |
| 频繁迭代 | Schema 即文档 |
| 聚合多个数据源 | BFF 层 |
GraphQL 实战
Schema 定义
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
users: [User!]!
post(id: ID!): Post
}
type Mutation {
createUser(name: String!, email: String!): User!
createPost(title: String!, content: String!, authorId: ID!): Post!
}
Resolver 实现
const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
return dataSources.userAPI.getUser(id);
},
users: async (_, __, { dataSources }) => {
return dataSources.userAPI.getUsers();
},
},
User: {
posts: async (user, _, { dataSources }) => {
return dataSources.postAPI.getPostsByUserId(user.id);
},
},
};
客户端查询
query GetUserWithPosts($id: ID!) {
user(id: $id) {
name
email
posts {
title
}
}
}
一次请求获取用户和文章,不需要多次请求。
性能考虑
N+1 问题
query {
users {
name
posts {
title
}
}
}
如果每个用户的 posts 都单独查询,性能很差。
解决:DataLoader 批量加载
const DataLoader = require('dataloader');
const postLoader = new DataLoader(async (userIds) => {
const posts = await getPostsByUserIds(userIds);
return userIds.map((id) => posts.filter((p) => p.authorId === id));
});
// 在 Resolver 中使用
User: {
posts: async (user, _, { postLoader }) => {
return postLoader.load(user.id);
},
},
查询复杂度
恶意用户可能发起深度嵌套查询:
query {
users {
posts {
author {
posts {
author {
# 无限嵌套...
}
}
}
}
}
}
解决:限制查询深度和复杂度
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
const validationRules = [
createComplexityLimitRule(1000, {
onCost: (cost) => console.log('query cost:', cost),
}),
];
缓存策略
REST 缓存
GET /api/users/1
Cache-Control: max-age=3600
浏览器和 CDN 自动缓存。
GraphQL 缓存
由于是 POST 请求,需要手动处理:
- 客户端缓存:Apollo Client / Relay
- 服务端缓存:响应缓存 + DataLoader
// Apollo Server 缓存
const server = new ApolloServer({
typeDefs,
resolvers,
cache: new RedisCache({
host: 'localhost',
}),
});
迁移经验
从 REST 迁移到 GraphQL:
- 先用 GraphQL 包装 REST 接口
- 逐步将逻辑下沉到 Resolver
- 最后移除中间层
// GraphQL 包装 REST
const resolvers = {
Query: {
user: async (_, { id }) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
},
},
};
团队反馈
我们团队用 GraphQL 两年后做的调研:
| 反馈 | 比例 |
|---|---|
| 很满意 | 60% |
| 一般 | 30% |
| 想换回 REST | 10% |
满意的原因:灵活、类型安全。 不满的原因:调试困难、缓存复杂。
总结
没有绝对的好坏,看场景选择:
- 简单 API、公共接口 → REST
- 复杂数据、多端需求 → GraphQL
不要为了技术而技术。如果 REST 够用,没必要换 GraphQL。