api-and-interface-design
指导稳定的 API 和接口设计。适用于设计 API、模块边界或任何公共接口,创建 REST 或 GraphQL 端点,定义模块间的类型契约,或建立前后端之间的边界。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
指导稳定的 API 和接口设计。适用于设计 API、模块边界或任何公共接口,创建 REST 或 GraphQL 端点,定义模块间的类型契约,或建立前后端之间的边界。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Manage git submodules for the learning-open-code mono-repo. Use when the user wants to: (1) Add a new git submodule — auto-detect or specify the category (open-ai-skills/open-sdd/open-ai-agent/open-ai-desktop/open-knowledge/open-productivity/open-java/open-trading/open-data), record the tracking branch in .gitmodules, clone the repo, and update README.md index. (2) Sync all existing submodules to their configured branches (git fetch + checkout branch + pull). (3) Update the root README.md with an up-to-date index of all synced projects grouped by category. (4) Initialize submodules after git clone — when open-*/ directories are empty or git submodule status returns nothing, guide through the full SOP (git submodule update --init --recursive [--remote]). Trigger keywords: submodule, git submodule, 子模块, add submodule, sync submodule, update submodule, submodule branch, README index, 更新索引, clone, init, 初始化子模块, submodule init, 拉取子模块.
对开源项目进行穷尽式教学文档生成——从宏观架构到微观实现的五层分级讲解,使用 Goal Loop 算法自主驱动完整代码覆盖。所有具体教学内容生成必须激活 `.agents/skills/teach/SKILL.md`。触发条件:用户要求"完整学习某个项目"、"生成项目架构文档"、"从入口到落地讲清楚每个功能"、"代码考古"、"源码分析"、或指定一个项目目录/仓库要求全面教学。
使用并行子 agent 为模块生成多个截然不同的接口设计。当用户想要设计 API、探索接口选项、比较模块形态,或提到 "设计两次" 时使用。
交互式 QA 会话,用户以对话方式报告 bug 或问题,agent 将其录入 GitHub Issue。在后台探索代码库以获取上下文和领域语言。当用户想要报告 bug、做 QA、以对话方式录入 issue,或提及 "QA session" 时使用。
通过用户访谈创建包含微小提交的详细重构计划,并将其录入 GitHub Issue。当用户想要规划重构、创建重构 RFC,或将重构分解为安全的渐进步骤时使用。
从当前对话中提取 DDD 风格的通用语言词汇表,标记歧义并提出规范术语。保存到 UBIQUITOUS_LANGUAGE.md。当用户想要定义领域术语、构建词汇表、固化术语、创建通用语言,或提到 "领域模型" 或 "DDD" 时使用。
| name | api-and-interface-design |
| description | 指导稳定的 API 和接口设计。适用于设计 API、模块边界或任何公共接口,创建 REST 或 GraphQL 端点,定义模块间的类型契约,或建立前后端之间的边界。 |
设计稳定、文档完善、难以误用的接口。好接口让正确的事容易做,让错误的事难以做。这适用于 REST API、GraphQL schema、模块边界、组件 props,以及任何代码之间进行通信的接触面。
当 API 的使用者数量足够多时,系统的所有可观察行为都会被某些人所依赖,无论你在契约中承诺了什么。
这意味着:每一个公共行为 —— 包括未文档化的怪异行为、错误消息文本、时序和排序 —— 一旦用户依赖它,就变成了事实上的契约。设计启示:
deprecation-and-migration 了解如何安全地移除用户已依赖的内容。避免强制消费者在同一依赖或 API 的多个版本之间选择。当不同消费者需要同一东西的不同版本时,就会出现钻石依赖问题。为只有一个版本存在的世界而设计 —— 扩展而非分叉。
在实现之前先定义接口。契约即规范 —— 实现紧随其后。
// 先定义契约
interface TaskAPI {
// 创建一个 task 并返回包含服务端生成字段的已创建 task
createTask(input: CreateTaskInput): Promise<Task>;
// 返回与筛选条件匹配的分页 tasks
listTasks(params: ListTasksParams): Promise<PaginatedResult<Task>>;
// 返回单个 task,如未找到则抛出 NotFoundError
getTask(id: string): Promise<Task>;
// 部分更新 —— 仅更新提供的字段
updateTask(id: string, input: UpdateTaskInput): Promise<Task>;
// 幂等删除 —— 即使已被删除也能成功
deleteTask(id: string): Promise<void>;
}
选定一种错误策略并在各处统一使用:
// REST: HTTP 状态码 + 结构化错误体
// 每个错误响应遵循相同的结构
interface APIError {
error: {
code: string; // 机器可读:"VALIDATION_ERROR"
message: string; // 人类可读:"Email is required"
details?: unknown; // 有帮助时提供额外上下文
};
}
// 状态码映射
// 400 → 客户端发送了无效数据
// 401 → 未认证
// 403 → 已认证但未授权
// 404 → 资源未找到
// 409 → 冲突(重复、版本不匹配)
// 422 → 验证失败(语义上无效)
// 500 → 服务端错误(绝不暴露内部细节)
不要混用模式。 如果某些端点抛异常,另一些返回 null,还有一些返回 { error } —— 消费者就无法预测行为。
信任内部代码。在外部输入进入系统的边界处进行验证:
// 在 API 边界处验证
app.post('/api/tasks', async (req, res) => {
const result = CreateTaskSchema.safeParse(req.body);
if (!result.success) {
return res.status(422).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid task data',
details: result.error.flatten(),
},
});
}
// 验证通过后,内部代码信任这些类型
const task = await taskService.create(result.data);
return res.status(201).json(task);
});
验证应放在哪里:
第三方 API 响应是不可信数据。 在将其用于任何逻辑、渲染或决策之前,验证其结构和内容。被入侵或行为异常的外部服务可能返回意外的类型、恶意内容或类似指令的文本。
验证不应放在哪里:
在不破坏现有消费者的情况下扩展接口:
// Good: 新增可选字段
interface CreateTaskInput {
title: string;
description?: string;
priority?: 'low' | 'medium' | 'high'; // 后续添加,可选
labels?: string[]; // 后续添加,可选
}
// Bad: 修改现有字段类型或删除字段
interface CreateTaskInput {
title: string;
// description: string; // 删除 —— 破坏现有消费者
priority: number; // 从 string 改为 number —— 破坏现有消费者
}
| 模式 | 约定 | 示例 |
|---|---|---|
| REST 端点 | 复数名词,不含动词 | GET /api/tasks、POST /api/tasks |
| 查询参数 | camelCase | ?sortBy=createdAt&pageSize=20 |
| 响应字段 | camelCase | { createdAt, updatedAt, taskId } |
| 布尔字段 | is/has/can 前缀 | isComplete、hasAttachments |
| 枚举值 | UPPER_SNAKE | "IN_PROGRESS"、"COMPLETED" |
GET /api/tasks → 列出 tasks(使用查询参数进行筛选)
POST /api/tasks → 创建一个 task
GET /api/tasks/:id → 获取单个 task
PATCH /api/tasks/:id → 更新一个 task(部分更新)
DELETE /api/tasks/:id → 删除一个 task
GET /api/tasks/:id/comments → 列出某 task 的评论(子资源)
POST /api/tasks/:id/comments → 为某 task 添加评论
为列表端点添加分页:
// Request
GET /api/tasks?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc
// Response
{
"data": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"totalItems": 142,
"totalPages": 8
}
}
使用查询参数进行筛选:
GET /api/tasks?status=in_progress&assignee=user123&createdAfter=2025-01-01
接受部分对象 —— 仅更新提供的内容:
// 仅 title 变化,其他内容保持不变
PATCH /api/tasks/123
{ "title": "Updated title" }
// Good: 每个变体明确清晰
type TaskStatus =
| { type: 'pending' }
| { type: 'in_progress'; assignee: string; startedAt: Date }
| { type: 'completed'; completedAt: Date; completedBy: string }
| { type: 'cancelled'; reason: string; cancelledAt: Date };
// 消费者获得类型收窄
function getStatusLabel(status: TaskStatus): string {
switch (status.type) {
case 'pending': return 'Pending';
case 'in_progress': return `In progress (${status.assignee})`;
case 'completed': return `Done on ${status.completedAt}`;
case 'cancelled': return `Cancelled: ${status.reason}`;
}
}
// Input: 调用者提供的内容
interface CreateTaskInput {
title: string;
description?: string;
}
// Output: 系统返回的内容(包含服务端生成的字段)
interface Task {
id: string;
title: string;
description: string | null;
createdAt: Date;
updatedAt: Date;
createdBy: string;
}
type TaskId = string & { readonly __brand: 'TaskId' };
type UserId = string & { readonly __brand: 'UserId' };
// 防止意外将 UserId 传递给需要 TaskId 的地方
function getTask(id: TaskId): Promise<Task> { ... }
| 借口 | 现实 |
|---|---|
| "我们以后再来写 API 文档" | 类型就是文档。先定义它们。 |
| "我们现在不需要分页" | 当有人有 100 条以上的数据时就会需要。从一开始就加上。 |
| "PATCH 太复杂了,直接用 PUT 吧" | PUT 每次都需要完整对象。PATCH 才是客户端真正需要的。 |
| "等需要时再做 API 版本管理" | 没有版本管理的破坏性变更会破坏消费者。从一开始就为扩展而设计。 |
| "没人用那个未文档化的行为" | Hyrum 定律:如果它是可观察的,就有人依赖它。把每个公共行为当作承诺。 |
| "我们可以维护两个版本" | 多版本会增加维护成本并造成钻石依赖问题。优先采用单一版本原则。 |
| "内部 API 不需要契约" | 内部消费者仍然是消费者。契约防止耦合并支持并行工作。 |
/api/createTask、/api/getUsers)设计 API 之后: