所属阶段:第四阶段「语言与框架」(第 17-22 课) 前置条件:第 17 课(后端语言)、第 20 课(数据库模式) 本课收获:一份符合 ECC 规范的 API 设计方案
一、本课概述
API 是前后端的契约,是微服务之间的桥梁。一个设计糟糕的 API 会让前端开发者抓狂,让后端维护变成噩梦。ECC 通过 api-design Skill 和框架专用 Skill 提供了一套完整的 API 设计规范。
本课回答三个问题:
- api-design Skill 的核心规范是什么? --- 从资源命名到错误响应
- 不同框架如何实现这些规范? --- Django、Spring Boot、NestJS 等
- 后端四层架构如何组织 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 |
服务维护中 |
关键原则 :401 和 403 的区别 --- 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-design 和 rules/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 分钟)
这是本课最重要的练习。
在第五节设计案例的基础上,补充以下内容:
- 状态转换接口:如何设计"将任务标记为完成"的接口?用 PATCH 还是专用端点?
- 批量操作:如何设计"批量删除任务"接口?
- 子资源:如何设计"任务评论"接口?写出完整的端点列表。
- 错误处理:为每个端点列出可能的错误状态码和错误码。
练习 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-architecture 和 skills/architecture-decision-records 目录。