API 设计最佳实践:REST vs GraphQL vs gRPC

API 设计最佳实践:REST vs GraphQL vs gRPC

设计 API 是现代开发的必修课。但选什么风格、怎么设计接口、如何处理错误,每个团队都有自己的经验。

这篇文章总结了我多年实战中沉淀下来的 API 设计经验,不分风格,只讲通用的最佳实践。


三种主流 API 风格

| 特性 | REST | GraphQL | gRPC |
|------|------|---------|------|
| 传输协议 | HTTP/1.1+ | HTTP | HTTP/2 |
| 数据格式 | JSON/XML | JSON | Protocol Buffers |
| 查询灵活性 | 固定结构 | 按需查询 | 预定义 |
| 缓存支持 | 原生 HTTP 缓存 | 需额外配置 | 需额外配置 |
| 学习成本 | 低 | 中 | 高 |
| 浏览器支持 | 原生 | 原生 | 需 gRPC-Web |

REST API 设计

URL 设计

javascript
// ✅ 好的设计
GET    /api/users              // 获取用户列表
GET    /api/users/:id          // 获取单个用户
POST   /api/users              // 创建用户
PUT    /api/users/:id          // 全量更新
PATCH  /api/users/:id          // 部分更新
DELETE /api/users/:id          // 删除用户

// 关联资源
GET /api/users/:id/orders      // 用户订单
GET /api/users/:id/orders/:oid // 用户的某个订单

// ❌ 不好的设计
GET    /api/getUsers           // 不应使用动词
POST   /api/updateUser         // 用 HTTP 方法表示动作
GET    /api/users?id=123       // 路径参数优于查询参数

响应格式

javascript
// ✅ 统一响应结构
{
  "status": "success",
  "data": {
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com",
    "createdAt": "2026-01-15T08:00:00Z"
  }
}

// ✅ 分页
{
  "status": "success",
  "data": [...],
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "total": 100,
    "totalPages": 5
  }
}

// ✅ 错误响应
{
  "status": "error",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "邮箱格式不正确",
    "details": [
      { "field": "email", "message": "必须是一个有效的邮箱地址" }
    ]
  }
}

HTTP 状态码

text
200 OK              - GET 成功
201 Created         - POST 创建成功
204 No Content      - DELETE 成功
400 Bad Request     - 参数错误
401 Unauthorized    - 未认证
403 Forbidden       - 无权限
404 Not Found       - 资源不存在
409 Conflict        - 资源冲突
422 Unprocessable   - 验证失败
429 Too Many Requests - 限流
500 Internal Server Error - 服务器错误

GraphQL 设计

Schema 定义

graphql
# typeDefs
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
  createdAt: DateTime!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
  comments: [Comment!]!
}

type Query {
  user(id: ID!): User
  users(page: Int, limit: Int): [User!]!
  postsByUser(userId: ID!): [Post!]!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
  deleteUser(id: ID!): Boolean!
}

查询示例

graphql
# ✅ 按需获取(GraphQL 的优势)
query {
  user(id: "1") {
    name
    email
    posts {
      title
      comments {
        content
      }
    }
  }
}

# ❌ 不要在一次查询中嵌套过深
query {
  users {
    posts {
      comments {
        user {
          posts {  # 过深嵌套会导致性能问题
            title
          }
        }
      }
    }
  }
}

最佳实践

javascript
// N+1 问题:使用 DataLoader
const DataLoader = require('dataloader');

const userLoader = new DataLoader(async (ids) => {
  const users = await db.users.findAll({ where: { id: ids } });
  return ids.map(id => users.find(u => u.id === id));
});

// 在 resolver 中使用
const resolvers = {
  Post: {
    author: (post) => userLoader.load(post.authorId),
  },
};

gRPC 设计

Proto 定义

protobuf
syntax = "proto3";

package user;

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
  rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
  rpc CreateUser (CreateUserRequest) returns (User);
  rpc UpdateUser (UpdateUserRequest) returns (User);
  rpc DeleteUser (DeleteUserRequest) returns (Empty);
  rpc SubscribeUsers (Empty) returns (stream User);  // 服务端流
}

message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
  string created_at = 4;
}

message GetUserRequest {
  int32 id = 1;
}

message ListUsersRequest {
  int32 page = 1;
  int32 page_size = 2;
}

流式通信

protobuf
// 服务端流式(数据推送)
service StockService {
  rpc SubscribePrice (StockRequest) returns (stream StockPrice);
}

// 客户端流式(批量上传)
service FileService {
  rpc UploadFile (stream FileChunk) returns (UploadResponse);
}

// 双向流式(实时通信)
service ChatService {
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

通用最佳实践

1. 版本管理

javascript
// URL 路径版本(REST)
/api/v1/users
/api/v2/users

// Header 版本
Accept: application/vnd.api+json;version=2

// GraphQL Schema 共存
const server = new ApolloServer({
  schema: [v1Schema, v2Schema],  // 逐步迁移
});

2. 认证与授权

javascript
// 推荐:Bearer Token
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

// 中间件示例
function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: '未认证' });
  
  try {
    req.user = jwt.verify(token, SECRET);
    next();
  } catch {
    res.status(401).json({ error: 'Token 无效' });
  }
}

3. 请求验证

javascript
// Zod 验证示例
import { z } from 'zod';

const createUserSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  age: z.number().min(0).max(150).optional(),
});

app.post('/api/users', (req, res) => {
  const result = createUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({
      error: 'VALIDATION_ERROR',
      details: result.error.issues,
    });
  }
  // result.data 是安全的
});

4. 速率限制

javascript
// 基于 Token Bucket 的实现
const rateLimit = new Map();

function rateLimiter(ip, limit = 100, window = 60000) {
  const now = Date.now();
  const record = rateLimit.get(ip) || { count: 0, reset: now + window };
  
  if (now > record.reset) {
    record.count = 0;
    record.reset = now + window;
  }
  
  record.count++;
  rateLimit.set(ip, record);
  
  if (record.count > limit) {
    throw { status: 429, message: '请求过于频繁' };
  }
}

5. 文档

javascript
// OpenAPI 3.0 规范(REST)
/**
 * @openapi
 * /api/users:
 *   get:
 *     summary: 获取用户列表
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *     responses:
 *       200:
 *         description: 用户列表
 */

选型决策树

text
项目是否需要实时/流式通信?
  ├── 是 → gRPC
  └── 否 → 客户端是否需要灵活的数据查询?
       ├── 是 → GraphQL
       └── 否 → REST

总结

设计 API 没有银弹。REST 简单直接适合大多数人,GraphQL 灵活强大适合数据复杂场景,gRPC 高性能适合微服务通信。

重要的是:一致性 —— 无论选哪种风格,在整个项目中保持一致,比选「最好」的风格更重要。

推荐指数:⭐⭐⭐⭐⭐

在线工具:https://swagger.io/tools/swagger-ui/

💬 评论区 (0)

暂无评论,快来抢沙发吧!