第 21 课:API 设计 — RESTful 模式与规范

所属阶段:第四阶段「语言与框架」(第 17-22 课) 前置条件:第 17 课(后端语言)、第 20 课(数据库模式) 本课收获:一份符合 ECC 规范的 API 设计方案


一、本课概述

API 是前后端的契约,是微服务之间的桥梁。一个设计糟糕的 API 会让前端开发者抓狂,让后端维护变成噩梦。ECC 通过 api-design Skill 和框架专用 Skill 提供了一套完整的 API 设计规范。

本课回答三个问题:

  1. api-design Skill 的核心规范是什么? --- 从资源命名到错误响应
  2. 不同框架如何实现这些规范? --- Django、Spring Boot、NestJS 等
  3. 后端四层架构如何组织 API 代码? --- Controller → Service → Repository → Database

二、api-design Skill 核心

2.1 资源命名

第一条规则:使用复数名词

bash 复制代码
✓ /api/v1/users           --- 用户集合
✓ /api/v1/users/123       --- 单个用户
✓ /api/v1/users/123/orders --- 用户的订单集合

✗ /api/v1/user             --- 单数
✗ /api/v1/getUsers         --- 动词
✗ /api/v1/user-list        --- 描述性名词

第二条规则:用 HTTP 方法表达动作

操作 HTTP 方法 URL 含义
查询列表 GET /users 获取用户列表
查询单个 GET /users/123 获取指定用户
创建 POST /users 创建新用户
全量更新 PUT /users/123 替换整个用户对象
部分更新 PATCH /users/123 更新部分字段
删除 DELETE /users/123 删除指定用户

第三条规则:嵌套资源不超过两层

bash 复制代码
✓ /users/123/orders              --- 两层:用户 → 订单
✓ /orders/456/items              --- 两层:订单 → 订单项

✗ /users/123/orders/456/items/789/reviews  --- 四层,太深了
✓ /order-items/789/reviews       --- 扁平化处理

2.2 HTTP 状态码选择

api-design Skill 定义了明确的状态码使用规则:

成功响应

状态码 使用场景 示例
200 OK GET 成功、PUT/PATCH 成功 返回查询结果或更新后的资源
201 Created POST 创建成功 返回新创建的资源 + Location 头
204 No Content DELETE 成功 不返回 body

客户端错误

状态码 使用场景 示例
400 Bad Request 请求格式错误、参数校验失败 缺少必填字段、类型错误
401 Unauthorized 未认证 未提供 token 或 token 过期
403 Forbidden 已认证但无权限 普通用户访问管理员接口
404 Not Found 资源不存在 用户 ID 不存在
409 Conflict 资源冲突 邮箱已注册
422 Unprocessable Entity 业务规则校验失败 余额不足
429 Too Many Requests 超出速率限制 包含 Retry-After 头

服务端错误

状态码 使用场景
500 Internal Server Error 未预期的服务端错误
502 Bad Gateway 上游服务不可用
503 Service Unavailable 服务维护中

关键原则401403 的区别 --- 401 表示"你是谁?"(认证),403 表示"我知道你是谁,但你没权限"(授权)。

2.3 分页与过滤

分页参数

bash 复制代码
GET /api/v1/users?page=2&limit=20

参数说明:
  page  --- 页码(从 1 开始)
  limit --- 每页数量(默认 20,最大 100)

过滤参数

bash 复制代码
GET /api/v1/users?status=active&role=admin&created_after=2024-01-01

规则:
  ✓ 使用 snake_case 参数名
  ✓ 时间格式用 ISO 8601
  ✓ 布尔值用 true/false
  ✗ 不要在 URL 中放 JSON

排序参数

bash 复制代码
GET /api/v1/users?sort=-created_at,name

规则:
  - 前缀表示升序(默认)
  + 或无前缀表示升序
  - 多字段排序用逗号分隔

2.4 错误响应格式

api-designrules/common/patterns.md 共同定义了标准的错误响应格式:

json 复制代码
{
  "success": false,
  "data": null,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数校验失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确"
      },
      {
        "field": "password",
        "message": "密码长度至少 8 位"
      }
    ]
  }
}

错误码分类

错误码前缀 含义 示例
AUTH_* 认证/授权 AUTH_TOKEN_EXPIRED
VALIDATION_* 参数校验 VALIDATION_ERROR
RESOURCE_* 资源相关 RESOURCE_NOT_FOUND
BUSINESS_* 业务规则 BUSINESS_INSUFFICIENT_BALANCE
SYSTEM_* 系统错误 SYSTEM_INTERNAL_ERROR

2.5 API Response 信封格式

rules/common/patterns.md 定义了统一的响应信封:

json 复制代码
// 成功响应(单个资源)
{
  "success": true,
  "data": {
    "id": "123",
    "name": "Alice",
    "email": "alice@example.com"
  },
  "error": null
}

// 成功响应(列表 + 分页元数据)
{
  "success": true,
  "data": [
    { "id": "1", "name": "Alice" },
    { "id": "2", "name": "Bob" }
  ],
  "error": null,
  "metadata": {
    "total": 150,
    "page": 1,
    "limit": 20,
    "totalPages": 8
  }
}

2.6 版本控制

bash 复制代码
URL 路径版本(推荐):
  /api/v1/users
  /api/v2/users

Header 版本:
  Accept: application/vnd.myapp.v2+json

规则:
  - 新版本不删除旧字段,只新增字段(向后兼容)
  - 旧版本至少维护 6 个月
  - 用 Sunset 头通知即将废弃

2.7 速率限制

yaml 复制代码
响应头示例:
  X-RateLimit-Limit: 100        --- 时间窗口内最大请求数
  X-RateLimit-Remaining: 67     --- 剩余请求数
  X-RateLimit-Reset: 1620000000 --- 重置时间(Unix 时间戳)

超限响应:
  HTTP/1.1 429 Too Many Requests
  Retry-After: 30

三、框架专用 API Skill

3.1 各框架的 API 层实现

不同框架有不同的 API 层组织方式,但 api-design 的规范是通用的:

框架 Skill API 层关键概念
Django django-patterns ViewSet、Serializer、Router(DRF)
Spring Boot springboot-patterns @RestController、@RequestMapping、ResponseEntity
Laravel laravel-patterns Route、Controller、Resource、FormRequest
NestJS nestjs-patterns @Controller、DTO、ValidationPipe
Ktor kotlin-ktor-patterns 路由 DSL、ContentNegotiation 插件

3.2 Django REST Framework 示例

python 复制代码
# serializers.py
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'name', 'email', 'created_at']
        read_only_fields = ['id', 'created_at']

# views.py
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [IsAuthenticated]
    pagination_class = PageNumberPagination
    filter_backends = [DjangoFilterBackend, OrderingFilter]
    filterset_fields = ['status', 'role']
    ordering_fields = ['created_at', 'name']

3.3 Spring Boot 示例

java 复制代码
@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    @GetMapping
    public ResponseEntity<ApiResponse<Page<UserDTO>>> list(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int limit) {
        Page<UserDTO> users = userService.findAll(PageRequest.of(page, limit));
        return ResponseEntity.ok(ApiResponse.success(users));
    }

    @PostMapping
    public ResponseEntity<ApiResponse<UserDTO>> create(
            @Valid @RequestBody CreateUserRequest request) {
        UserDTO user = userService.create(request);
        return ResponseEntity.status(HttpStatus.CREATED)
            .body(ApiResponse.success(user));
    }
}

3.4 Ktor 路由 DSL 示例

kotlin 复制代码
fun Route.userRoutes(userService: UserService) {
    route("/api/v1/users") {
        get {
            val page = call.parameters["page"]?.toIntOrNull() ?: 1
            val limit = call.parameters["limit"]?.toIntOrNull() ?: 20
            val users = userService.findAll(page, limit)
            call.respond(ApiResponse.success(users))
        }

        post {
            val request = call.receive<CreateUserRequest>()
            val user = userService.create(request)
            call.respond(HttpStatusCode.Created, ApiResponse.success(user))
        }
    }
}

四、后端四层架构

4.1 backend-patterns Skill

backend-patterns Skill 定义了标准的后端四层架构:

sql 复制代码
┌──────────────────────────────────────┐
│           Controller 层              │
│  接收请求 → 参数校验 → 调用 Service  │
│  不含业务逻辑                         │
├──────────────────────────────────────┤
│           Service 层                 │
│  业务逻辑 → 编排 Repository 调用      │
│  事务管理在这一层                      │
├──────────────────────────────────────┤
│           Repository 层              │
│  数据访问 → CRUD 操作                 │
│  封装 SQL/ORM 查询                    │
├──────────────────────────────────────┤
│           Database 层                │
│  PostgreSQL / MySQL / MongoDB        │
└──────────────────────────────────────┘

4.2 各层职责边界

可以做 不可以做
Controller 参数校验、请求/响应格式转换、调用 Service 直接操作数据库、包含业务逻辑
Service 业务逻辑、事务管理、调用多个 Repository 直接操作 HTTP 对象、直接写 SQL
Repository CRUD 操作、查询构建、缓存 包含业务逻辑、操作 HTTP 对象
Database 存储数据、执行 SQL、索引 包含应用逻辑

4.3 为什么要分层?

复制代码
不分层的问题:

Controller 直接操作数据库
  → 同一个查询在多个 Controller 中重复
  → 换数据库需要改所有 Controller
  → 无法单独测试业务逻辑

分层的好处:

Controller 只负责 HTTP 相关逻辑
  → Service 可以被多个 Controller 复用
  → Repository 可以被多个 Service 复用
  → 换数据库只改 Repository 层
  → 每层可以独立测试

五、API 设计实战

5.1 设计案例:Task 资源

让我们为一个 Task(任务)资源设计完整的 RESTful API:

资源定义

json 复制代码
{
  "id": "uuid",
  "title": "string (required, 1-255 chars)",
  "description": "string (optional, max 2000 chars)",
  "status": "enum: pending | in_progress | completed | cancelled",
  "priority": "enum: low | medium | high | urgent",
  "assignee_id": "uuid (optional)",
  "due_date": "ISO 8601 datetime (optional)",
  "created_at": "ISO 8601 datetime (read-only)",
  "updated_at": "ISO 8601 datetime (read-only)"
}

端点设计

方法 URL 描述 状态码
GET /api/v1/tasks 查询任务列表 200
GET /api/v1/tasks/:id 查询单个任务 200 / 404
POST /api/v1/tasks 创建任务 201 / 400
PATCH /api/v1/tasks/:id 更新任务 200 / 404 / 400
DELETE /api/v1/tasks/:id 删除任务 204 / 404

列表查询参数

bash 复制代码
GET /api/v1/tasks?status=pending&priority=high&assignee_id=uuid&sort=-due_date&page=1&limit=20

创建请求

json 复制代码
POST /api/v1/tasks
Content-Type: application/json

{
  "title": "实现用户注册功能",
  "description": "包含邮箱验证和密码强度校验",
  "priority": "high",
  "assignee_id": "550e8400-e29b-41d4-a716-446655440000",
  "due_date": "2026-04-15T23:59:59Z"
}

成功响应

json 复制代码
HTTP/1.1 201 Created
Location: /api/v1/tasks/660e8400-e29b-41d4-a716-446655440001

{
  "success": true,
  "data": {
    "id": "660e8400-e29b-41d4-a716-446655440001",
    "title": "实现用户注册功能",
    "description": "包含邮箱验证和密码强度校验",
    "status": "pending",
    "priority": "high",
    "assignee_id": "550e8400-e29b-41d4-a716-446655440000",
    "due_date": "2026-04-15T23:59:59Z",
    "created_at": "2026-04-08T10:30:00Z",
    "updated_at": "2026-04-08T10:30:00Z"
  },
  "error": null
}

校验失败响应

json 复制代码
HTTP/1.1 400 Bad Request

{
  "success": false,
  "data": null,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数校验失败",
    "details": [
      { "field": "title", "message": "标题不能为空" },
      { "field": "priority", "message": "优先级必须是 low/medium/high/urgent 之一" }
    ]
  }
}

六、本课练习

练习 1:查看 api-design Skill(10 分钟)

bash 复制代码
ls skills/api-design/

回答问题:

  • Skill 中关于版本控制推荐了哪种方式?
  • 对于批量操作(如批量删除),推荐的端点设计是什么?

练习 2:为 Task 资源设计完整 API(25 分钟)

这是本课最重要的练习。

在第五节设计案例的基础上,补充以下内容:

  1. 状态转换接口:如何设计"将任务标记为完成"的接口?用 PATCH 还是专用端点?
  2. 批量操作:如何设计"批量删除任务"接口?
  3. 子资源:如何设计"任务评论"接口?写出完整的端点列表。
  4. 错误处理:为每个端点列出可能的错误状态码和错误码。

练习 3:审查现有 API(15 分钟)

选择你当前项目的一个 API,用 api-design Skill 的规范审查它:

  • 资源命名是否使用复数名词?
  • 状态码使用是否准确?
  • 错误响应是否包含足够的信息?
  • 分页参数格式是否一致?

练习 4(选做):思考题

REST API 和 GraphQL 各有什么优缺点?在什么场景下你会选择 GraphQL 而不是 REST?ECC 的 api-design Skill 的哪些原则(如错误格式、版本控制)在 GraphQL 中仍然适用?


七、本课小结

你应该记住的 内容
资源命名 复数名词 + HTTP 方法表达动作 + 嵌套不超过两层
状态码 401 是认证,403 是授权,422 是业务规则
响应格式 信封格式:success + data + error + metadata
四层架构 Controller → Service → Repository → Database
框架 Skill Django/Spring/NestJS/Ktor 各有专用 API Skill

八、下节预告

第 22 课:软件架构 --- 六边形、微服务与决策记录

下节课我们将从 API 设计上升到系统架构层面。你将学习六边形架构(Ports & Adapters)、架构决策记录(ADR),以及 ECC 的 architect Agent 如何辅助架构决策。

预习建议 :提前浏览 skills/hexagonal-architectureskills/architecture-decision-records 目录。

相关推荐
王小酱2 小时前
第 18 课:前端框架 — React / Next.js / Vue / Nuxt
openai·ai编程
王小酱2 小时前
第 12 课:调用链追踪 — 从 Command 到执行
openai·ai编程·airbnb
王小酱2 小时前
第 13 课:TDD 全流程 — RED-GREEN-IMPROVE
openai·ai编程·aiops
王小酱2 小时前
第 14 课:验证循环 — 从代码到可提交
openai·ai编程·aiops
王小酱2 小时前
第 26 课:Eval 驱动开发 — 衡量 AI 行为
ai编程
王小酱2 小时前
第 19 课:移动端开发 — Swift / SwiftUI / Dart / Flutter
ai编程
王小酱2 小时前
第 25 课:持续学习 — Instinct 提取与演化
ai编程
王小酱2 小时前
第 20 课:数据库模式 — 设计、迁移与优化
ai编程
王小酱2 小时前
第 24 课:安全(下)— 防御机制与实战
ai编程