بنقرة واحدة
api-and-interface-design
指导稳定的 API 和接口设计。设计 API、模块边界或任何公共接口时使用。创建 REST 或 GraphQL endpoint、定义模块之间的类型契约,或建立前后端边界时使用。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
指导稳定的 API 和接口设计。设计 API、模块边界或任何公共接口时使用。创建 REST 或 GraphQL endpoint、定义模块之间的类型契约,或建立前后端边界时使用。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
在真实浏览器中测试。构建或调试任何在浏览器中运行的内容时使用。当你需要通过 Chrome DevTools MCP 检查 DOM、捕获 console 错误、分析网络请求、分析性能,或用真实运行时数据验证视觉输出时使用。
自动化 CI/CD pipeline 设置。用于设置或修改构建和部署 pipeline 时;用于需要自动化质量门禁、在 CI 中配置 test runners,或建立部署策略时。
执行多维度代码审查。用于合并任何变更之前;用于审查自己、其他 agent 或人类编写的代码;用于在代码进入主分支前从多个维度评估代码质量。
为清晰度简化代码。用于在不改变行为的前提下重构代码以提升清晰度;用于代码能运行但比应有状态更难阅读、维护或扩展时;用于审查已累积不必要复杂度的代码时。
优化 agent 上下文设置。当开始新会话、agent 输出质量下降、在任务之间切换,或需要为项目配置规则文件和上下文时使用。
指导系统化根因调试。当测试失败、构建中断、行为不符合预期,或遇到任何意外错误时使用。当你需要系统化地找到并修复根因,而不是猜测时使用。
| name | api-and-interface-design |
| description | 指导稳定的 API 和接口设计。设计 API、模块边界或任何公共接口时使用。创建 REST 或 GraphQL endpoint、定义模块之间的类型契约,或建立前后端边界时使用。 |
设计稳定、文档清晰且难以误用的接口。好的接口让正确的事情容易做,让错误的事情难做。这适用于 REST API、GraphQL schema、模块边界、组件 props,以及任何一段代码与另一段代码对话的表面。
只要 API 用户足够多,系统中所有可观察行为都会被某些人依赖,无论你在契约中承诺了什么。
这意味着:每个公共行为,包括未记录的怪癖、错误消息文本、时序和排序,一旦被用户依赖,就会成为事实契约。设计含义:
deprecation-and-migration。避免迫使消费者在同一个依赖或 API 的多个版本之间选择。当不同消费者需要同一事物的不同版本时,就会出现 diamond dependency 问题。为一次只存在一个版本的世界设计:扩展,而不是 fork。
先定义接口,再实现它。契约就是规格,实现随后而来。
// Define the contract first
interface TaskAPI {
// Creates a task and returns the created task with server-generated fields
createTask(input: CreateTaskInput): Promise<Task>;
// Returns paginated tasks matching filters
listTasks(params: ListTasksParams): Promise<PaginatedResult<Task>>;
// Returns a single task or throws NotFoundError
getTask(id: string): Promise<Task>;
// Partial update — only provided fields change
updateTask(id: string, input: UpdateTaskInput): Promise<Task>;
// Idempotent delete — succeeds even if already deleted
deleteTask(id: string): Promise<void>;
}
选择一种错误策略,并在所有地方使用它:
// REST: HTTP status codes + structured error body
// Every error response follows the same shape
interface APIError {
error: {
code: string; // Machine-readable: "VALIDATION_ERROR"
message: string; // Human-readable: "Email is required"
details?: unknown; // Additional context when helpful
};
}
// Status code mapping
// 400 → Client sent invalid data
// 401 → Not authenticated
// 403 → Authenticated but not authorized
// 404 → Resource not found
// 409 → Conflict (duplicate, version mismatch)
// 422 → Validation failed (semantically invalid)
// 500 → Server error (never expose internal details)
不要混用模式。 如果有些 endpoint throw,有些返回 null,有些返回 { error },消费者就无法预测行为。
信任内部代码。在外部输入进入系统的边缘进行验证:
// Validate at the API boundary
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(),
},
});
}
// After validation, internal code trusts the types
const task = await taskService.create(result.data);
return res.status(201).json(task);
});
验证应该放在:
第三方 API 响应是不可信数据。 在任何逻辑、渲染或决策中使用前,先验证其形态和内容。被攻陷或异常的外部服务可能返回意外类型、恶意内容或类似指令的文本。
验证不应该放在:
扩展接口时不要破坏现有消费者:
// Good: Add optional fields
interface CreateTaskInput {
title: string;
description?: string;
priority?: 'low' | 'medium' | 'high'; // Added later, optional
labels?: string[]; // Added later, optional
}
// Bad: Change existing field types or remove fields
interface CreateTaskInput {
title: string;
// description: string; // Removed — breaks existing consumers
priority: number; // Changed from string — breaks existing consumers
}
| 模式 | 约定 | 示例 |
|---|---|---|
| REST endpoints | 复数名词,不用动词 | GET /api/tasks, POST /api/tasks |
| Query params | camelCase | ?sortBy=createdAt&pageSize=20 |
| Response fields | camelCase | { createdAt, updatedAt, taskId } |
| Boolean fields | is/has/can 前缀 | isComplete, hasAttachments |
| Enum values | UPPER_SNAKE | "IN_PROGRESS", "COMPLETED" |
GET /api/tasks → List tasks (with query params for filtering)
POST /api/tasks → Create a task
GET /api/tasks/:id → Get a single task
PATCH /api/tasks/:id → Update a task (partial)
DELETE /api/tasks/:id → Delete a task
GET /api/tasks/:id/comments → List comments for a task (sub-resource)
POST /api/tasks/:id/comments → Add a comment to a task
对 list endpoints 分页:
// Request
GET /api/tasks?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc
// Response
{
"data": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"totalItems": 142,
"totalPages": 8
}
}
使用 query parameters 表示 filters:
GET /api/tasks?status=in_progress&assignee=user123&createdAfter=2025-01-01
接受 partial objects,只更新提供的字段:
// Only title changes, everything else preserved
PATCH /api/tasks/123
{ "title": "Updated title" }
// Good: Each variant is explicit
type TaskStatus =
| { type: 'pending' }
| { type: 'in_progress'; assignee: string; startedAt: Date }
| { type: 'completed'; completedAt: Date; completedBy: string }
| { type: 'cancelled'; reason: string; cancelledAt: Date };
// Consumer gets type narrowing
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: what the caller provides
interface CreateTaskInput {
title: string;
description?: string;
}
// Output: what the system returns (includes server-generated fields)
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' };
// Prevents accidentally passing a UserId where a TaskId is expected
function getTask(id: TaskId): Promise<Task> { ... }
| 自我合理化 | 现实 |
|---|---|
| “我们之后再写 API 文档” | 类型本身就是文档。先定义它们。 |
| “现在还不需要分页” | 一旦有人有 100+ items,立刻就需要。从一开始就加上。 |
| “PATCH 很复杂,我们直接用 PUT” | PUT 要求每次传完整对象。PATCH 才是客户端真正想要的。 |
| “需要时再给 API 做版本化” | 没有版本化的 breaking changes 会破坏消费者。从一开始就为扩展而设计。 |
| “没人用那个未记录行为” | Hyrum's Law:只要可观察,就会有人依赖。把每个公共行为当作承诺。 |
| “我们可以同时维护两个版本” | 多版本会放大维护成本,并制造 diamond dependency 问题。优先采用单版本规则。 |
| “内部 API 不需要契约” | 内部消费者也是消费者。契约能防止耦合,并支持并行工作。 |
/api/createTask、/api/getUsers)设计 API 后: