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 之后: