RESTful 与契约优先:规范接口定义,统一接口设计范式

先把接口规则定清楚,再让 AI 写代码。

在 AI 辅助开发中,接口设计是一个非常容易"看起来能用,最后越来越乱"的地方。

让 AI 帮你写几个接口,它可能很快就生成:

text 复制代码
/getUser
/createUser
/update_user
/delete-user
/userList

功能都能跑。但项目继续发展,就很容易出现:

text 复制代码
同一个资源,不同命名
同一个动作,不同 HTTP 方法
同一种错误,不同返回结构
同一个分页,不同字段名称

最后前端、后端、测试人员,甚至 AI 自己都开始猜:

"这个接口到底应该怎么调用?"

这时候真正缺少的,并不是代码能力,而是:

统一的接口契约。


📌 技术名片

RESTful API(Representational State Transfer Application Programming Interface,表述性状态转移应用程序编程接口)

是一种常见的网络接口设计风格。

它强调围绕"资源"设计 URL,并通过 HTTP 方法表达操作。

例如:

text 复制代码
GET    /agents
POST   /agents
GET    /agents/12
PUT    /agents/12
DELETE /agents/12

这里:

  • GET:读取资源
  • POST:创建资源
  • PUT:整体更新资源
  • PATCH:局部修改资源
  • DELETE:删除资源

而:

Contract First(契约优先)

指的是:

先定义接口长什么样,再实现接口内部逻辑。

也就是说,先确定:

text 复制代码
URL 是什么?
HTTP 方法是什么?
输入字段是什么?
输出字段是什么?
错误如何返回?
状态码是什么?

然后开发者或者 AI 再按照这份契约实现代码。


一个通俗比喻:接口就是餐厅菜单

可以把软件接口想象成一家餐厅的菜单。

菜单上写着:

text 复制代码
宫保鸡丁
价格:38 元
辣度:中辣
份量:1 份

顾客不需要知道:

text 复制代码
厨师是谁
锅是什么牌子
后厨怎么切菜
鸡肉放多少油

顾客只需要知道:

我按照菜单点菜,你按照菜单给我菜。

API 也是一样。

前端不应该关心:

text 复制代码
数据库怎么查
Service 怎么实现
Repository 怎么写

它只需要知道:

text 复制代码
请求地址
请求方式
参数格式
返回结果
错误格式

所以接口契约就像:

软件系统对外公布的菜单。

如果菜单每天都变:

text 复制代码
昨天叫"宫保鸡丁"
今天叫"辣子鸡丁"
明天改成"鸡肉套餐 A"

顾客一定会崩溃,接口也是一样。


一、RESTful 的核心:URL 表达"资源",HTTP 方法表达"动作"

这是理解 RESTful 最重要的一点。

很多项目一开始会这样写:

text 复制代码
/getAgent
/createAgent
/updateAgent
/deleteAgent

这种写法本质上是:

把动作写进 URL。

而 RESTful 更推荐:

text 复制代码
GET    /agents
POST   /agents
PUT    /agents/{agent_id}
DELETE /agents/{agent_id}

因为:

text 复制代码
/agents

代表的是资源:

Agent 集合。

至于"查、增、改、删",交给 HTTP 方法表达。

于是接口语言会变得非常统一:

text 复制代码
资源 + HTTP 方法

而不是:

text 复制代码
资源 + 自定义动词 + 自定义命名习惯

二、为什么这种规则对 AI 编程特别重要?

人类开发者在一个项目里工作几年以后,通常会逐渐形成习惯。

可是 AI 不一样,AI 每次生成代码时,本质上都在重新推理:

text 复制代码
这个接口应该叫什么?
这个更新应该用 POST 还是 PUT?
删除成功返回什么?
分页字段叫什么?

如果没有架构规则,AI 完全可能第一次生成:

text 复制代码
POST /createAgent

下一次又生成:

text 复制代码
POST /agents/create

再下一次变成:

text 复制代码
POST /agent

三套接口都能工作,但项目已经开始失控。

所以:

RESTful 不是为了追求"教科书漂亮",而是为了减少 AI 的自由发挥空间。


三、接口设计规则

1. 统一资源命名

资源名称建议使用:

text 复制代码
名词
复数
统一命名风格

例如:

text 复制代码
/users
/agents
/tools
/documents
/knowledge-bases

而不是:

text 复制代码
/userList
/getAgents
/toolManage
/queryDocument

miniagent 当前后台接口就采用了比较明确的资源式路径。

例如主程序统一注册:

python 复制代码
app.include_router(
    admin_agent_router,
    prefix="/api/v1/admin/agents",
    tags=["Admin - Agent"]
)

app.include_router(
    admin_tool_router,
    prefix="/api/v1/admin/tools",
    tags=["Admin - Tool"]
)

app.include_router(
    admin_document_router,
    prefix="/api/v1/admin/documents",
    tags=["Admin - Document"]
)

可以看到资源命名保持了:

text 复制代码
/agents
/tools
/documents
/knowledge-bases
/prompts
/system-settings

而没有把:

text 复制代码
get
create
delete
update

这些动作塞进 URL。


2. HTTP 方法表达动作

miniagent 的 Agent 管理接口为例。

读取列表:

python 复制代码
@router.get("")
async def list_agents(...):

读取单个 Agent:

python 复制代码
@router.get("/{agent_id}")
async def get_agent(...):

创建:

python 复制代码
@router.post("")
async def create_agent(...):

更新:

python 复制代码
@router.put("/{agent_id}")
async def update_agent(...):

删除:

python 复制代码
@router.delete("/{agent_id}")
async def delete_agent(...):

组合起来就是:

text 复制代码
GET    /api/v1/admin/agents
GET    /api/v1/admin/agents/{agent_id}
POST   /api/v1/admin/agents
PUT    /api/v1/admin/agents/{agent_id}
DELETE /api/v1/admin/agents/{agent_id}

即使你从来没看过 miniagent 的代码,也基本可以猜出这些接口的含义。

这就是好的接口设计带来的:

可预测性。


3. PUT 和 PATCH 不要乱用

这是 AI 非常容易混淆的地方。

PUT 通常表示:

更新一个资源。

例如:

text 复制代码
PUT /agents/12

表示更新编号 12 的 Agent。

PATCH 表示:

Partial Update(局部更新)

即只修改资源的一部分状态。

miniagent 中就有一个很直观的例子:

python 复制代码
@router.patch("/{agent_id}/toggle")
async def toggle_agent_active(...):

这里并不是重新提交整个 Agent,而只是:

切换 Agent 是否启用。

所以使用 PATCH 比较符合语义。

对于初学者来说,可以先记住:

text 复制代码
PUT   → 更新资源
PATCH → 局部修改

4. RESTful 也不是"绝对不能出现动作"

很多人学 RESTful 后容易走向另一个极端:

URL 里绝对不能出现任何动作。

其实没有必要。

有些业务操作本身并不是简单的 CRUD。

CRUD 全称:

Create、Read、Update、Delete(创建、读取、更新、删除)

例如:

text 复制代码
/agents/{id}/toggle

表达:

切换 Agent 状态。

再比如未来可能有:

text 复制代码
/documents/{id}/reindex
/tasks/{id}/cancel
/agents/{id}/run

这些本质上是业务命令。

重点不是机械追求"纯 REST",而是:

资源型操作遵循统一范式,特殊业务动作有明确且稳定的命名规则。


5. 契约不仅包含 URL

很多项目认为:

URL 统一了,就算接口规范完成了。

其实远远不够,一个完整接口契约至少应该包括:

text 复制代码
请求路径
HTTP 方法
Path 参数
Query 参数
Request Body
Response Body
HTTP Status Code
错误模型

其中:

Path Parameter

路径参数

例如:

text 复制代码
/agents/{agent_id}

Query Parameter

查询参数

例如:

text 复制代码
/agents?page=1&page_size=20

Request Body

请求体

即客户端发送的数据主体。

Response Body

响应体

即服务端返回的数据主体。

HTTP Status Code

HTTP 状态码

例如:

text 复制代码
200 OK
201 Created
404 Not Found
409 Conflict
500 Internal Server Error

6. 查询参数也必须统一

假设一个系统里出现:

text 复制代码
?page=1&pageSize=20

另一个接口:

text 复制代码
?pageIndex=1&limit=20

再一个:

text 复制代码
?offset=0&size=20

每一种都能工作。

但问题是:

前端永远记不住。

miniagent 的 Agent 列表接口采用:

python 复制代码
page: int = Query(1, ge=1)
page_size: int = Query(20, ge=1, le=100)

并且分页结果定义为:

python 复制代码
class PageResult(BaseModel, Generic[T]):
    total: int
    page: int
    page_size: int
    data: List[T]

这样整个项目都可以统一成:

text 复制代码
page
page_size
total
data

而不是每个模块重新发明一次。


7. 接口响应也必须有统一"外壳"

这是契约优先中非常重要的一部分。

最容易失控的场景是:

用户接口返回:

json 复制代码
{
  "success": true,
  "user": {}
}

Agent 接口返回:

json 复制代码
{
  "code": 0,
  "result": {}
}

知识库接口又返回:

json 复制代码
{
  "status": "ok",
  "data": {}
}

前端必须针对每个模块分别判断。

这就是:

接口契约碎片化。

miniagent 定义了统一响应模型:

python 复制代码
class ApiResponse(BaseModel, Generic[T]):
    code: int = 200
    message: str = "success"
    data: Optional[T] = None

因此成功响应可以统一成:

json 复制代码
{
  "code": 200,
  "message": "success",
  "data": {}
}

没有数据时也可以是:

json 复制代码
{
  "code": 200,
  "message": "success"
}

这种设计最重要的价值不是少写几行代码。

而是:

前端只需要学习一次返回协议。


8. HTTP 状态码和业务返回结构要各司其职

miniagent 创建 Agent 时:

python 复制代码
@router.post(
    "",
    response_model=ApiResponse,
    status_code=status.HTTP_201_CREATED
)

这里使用:

text 复制代码
201 Created

表示:

服务器成功创建了一个新资源。

而读取或更新通常返回:

text 复制代码
200 OK

如果资源不存在,则全局异常处理返回:

text 复制代码
404

已经存在则:

text 复制代码
409

普通业务请求错误:

text 复制代码
400

系统内部错误:

text 复制代码
500

miniagent 在全局异常处理中已经统一映射这些错误类型。

这实际上建立了:

text 复制代码
领域异常
    ↓
统一异常处理
    ↓
HTTP 状态码
    ↓
统一 ApiResponse

因此业务层不用自己决定:

text 复制代码
到底返回 200?
还是返回 404?
错误 JSON 长什么样?

9. 契约优先真正解决的是"谁说了算"

如果没有契约,开发过程很容易变成:

text 复制代码
前端认为应该这样
        ↓
后端写成另一种
        ↓
AI 又生成第三种
        ↓
联调时再修改

而契约优先变成:

text 复制代码
先定义接口
      ↓
确定输入
      ↓
确定输出
      ↓
确定状态码
      ↓
前端实现
      ↓
后端实现
      ↓
自动测试

接口成为:

前后端共同遵守的协议。


10. FastAPI 天然适合契约优先

FastAPI 是:

一个基于 Python 类型标注构建 Web API 的框架。

其中一个重要特点就是:

可以直接根据代码中的类型定义生成 API 文档。

例如:

python 复制代码
async def create_agent(
    payload: AgentCreate,
):

这里的:

text 复制代码
AgentCreate

本身就是输入契约。

而:

python 复制代码
response_model=ApiResponse

则声明:

输出必须符合 ApiResponse 契约。

miniagent 在 FastAPI 初始化中还开启了:

python 复制代码
docs_url="/docs"
redoc_url="/redoc"

FastAPI 会生成基于 OpenAPI 的接口文档。

OpenAPI

OpenAPI Specification(开放应用程序接口规范)

是一种机器可读的 API 描述标准。

这样接口契约不只存在于人的脑子里,还可以被:

text 复制代码
前端
测试工具
API 调试工具
代码生成器
AI

直接读取。


11. RESTful + Schema + ApiResponse 才是一整套接口规范

单独使用 RESTful 并不能解决全部问题。

真正稳定的接口体系通常是:

text 复制代码
RESTful URL
      +
HTTP Method
      +
Schema
      +
Status Code
      +
Unified Response
      +
Unified Exception

其中 Schema 可以理解为:

数据结构契约。

例如:

text 复制代码
AgentCreate
AgentUpdate
AgentOut

分别定义:

text 复制代码
创建 Agent 允许传什么
更新 Agent 允许传什么
返回 Agent 时包含什么

而不是一个:

python 复制代码
dict

走遍天下。


四、有什么好处?

统一接口设计后,最明显的变化不是"代码更漂亮",而是整个团队的认知成本下降。

1. 接口可以猜

看到:

text 复制代码
GET /agents/12

不用查文档也基本知道:

获取 Agent 12。

看到:

text 复制代码
DELETE /agents/12

也能马上知道:

删除 Agent 12。

这就是:

一致性带来的可预测性。

2. 前后端沟通成本下降

不需要每新增一个接口都讨论:

text 复制代码
到底叫 getAgentById 还是 queryAgent?

规则已经确定。

3. 测试更容易自动化

接口一旦规范化:

text 复制代码
POST   创建
GET    查询
PUT    更新
DELETE 删除

自动测试工具更容易批量生成测试。

4. 文档更稳定

FastAPI 可以根据:

text 复制代码
Router
Schema
Response Model

直接生成 API 文档。

接口定义本身就是文档的一部分。

5. 更适合 AI 编程

这一点尤其重要。

AI 不需要猜:

text 复制代码
接口应该叫什么?
响应怎么包装?
错误应该怎么办?
分页怎么设计?

它只需要遵循规则。


五、把接口规范直接写进 AI 项目规则

只有架构设计还不够,如果希望 AI 长期遵守,就要把这些约束写进项目规则。

例如:

text 复制代码
## API 设计规则

1. 遵循 RESTful 资源导向型 API 设计。
2. URL 必须使用名词,而非 CRUD 动词。
3. 在适当情况下使用资源名称的复数形式。
4. 使用 GET 方法读取资源。
5. 使用 POST 方法创建资源。
6. 使用 PUT 方法更新资源。
7. 使用 PATCH 方法进行部分状态更改。
8. 使用 DELETE 方法删除数据。
9. 所有常规 API 响应必须使用 ApiResponse 类型。
10. 分页响应必须使用 PageResult 类型。
11. 请求和响应数据必须使用 Pydantic schema。
12. 路由器不得返回任意字典结构。
13. 路由器仅处理 HTTP 相关请求。
14. 业务逻辑应放在 Service 类中。
15. 业务错误必须作为域异常抛出。
16. 单个路由器内部不得重复异常处理。
17. 在引入新端点之前,重用现有的 API 模式。

简单来说就是:

text 复制代码
URL 只描述资源
       ↓
HTTP 方法表达动作
       ↓
Schema 定义输入输出
       ↓
ApiResponse 统一返回
       ↓
领域异常统一处理
       ↓
Router 不写业务逻辑

这样 AI 每次生成新接口之前,就已经知道:

不能重新设计一套接口风格。


六、提示词怎么写才真正有用?

不要只告诉 AI:

text 复制代码
请帮我写一个 Agent CRUD 接口。

CRUD 即:

Create、Read、Update、Delete(创建、读取、更新、删除)

更好的提示词是:

text 复制代码
请为 Agent 资源实现管理接口。

必须遵循项目现有 API 契约:

- RESTful 资源式 URL
- 复用 /api/v1/admin/agents 路径结构
- GET 查询
- POST 创建
- PUT 更新
- DELETE 删除
- 局部状态修改使用 PATCH
- 请求参数使用现有 Pydantic Schema
- 返回值统一使用 ApiResponse
- 分页使用 PageResult
- Router 只处理 HTTP 协议和依赖注入
- 所有业务逻辑放入 AgentService
- 业务异常使用已有领域异常体系
- 禁止在 Router 内重新定义异常返回格式
- 实现前先参考项目中已有 Agent、Tool、KnowledgeBase API 风格

这时候 AI 的任务就从:

"帮我设计并写一个接口。"

变成:

"按照已有合同施工。"

两者生成代码的稳定性完全不同。


七、正面产出:miniagent 已经形成的接口范式

miniagent) 当前真实代码组合起来,可以看到一套比较清晰的接口链路。

简单来看:

text 复制代码
HTTP Request
      │
      ▼
FastAPI Router
      │
      ├── Path / Query
      ├── Pydantic Schema
      ├── Permission
      └── HTTP Method
      │
      ▼
Service
      │
      ▼
Domain / Repository / Runtime
      │
      ▼
Result
      │
      ▼
ApiResponse
      │
      ▼
HTTP Response

如果业务过程中发生异常:

text 复制代码
Service
   │
   ▼
Domain Exception
   │
   ▼
Global Exception Handler
   │
   ├── 400
   ├── 404
   ├── 409
   └── 500
   │
   ▼
ApiResponse

这几个部分都已经在 miniagent 中有真实实现:

  • Router 使用 GET / POST / PUT / PATCH / DELETE 描述操作。
  • 输入输出通过 Pydantic Schema 建模。
  • 所有普通响应统一使用 ApiResponse
  • 分页结果统一使用 PageResult
  • 业务逻辑从 Router 下沉到 Service。
  • 领域异常由全局异常处理统一转换为 HTTP 状态码和响应结构。
  • FastAPI 自动提供 /docs/redoc 接口文档入口。

最终形成的不是几个孤立接口,而是一套:

可预测、可复用、可扩展、可让 AI 持续遵守的接口设计范式。


写在最后

很多人第一次接触 RESTful,会觉得它只是:

text 复制代码
GET 查
POST 增
PUT 改
DELETE 删

但真正进入工程实践后就会发现:

RESTful 的核心价值其实是统一语言。

而契约优先则进一步解决:

在代码实现之前,先把这套语言固定下来。

对于传统开发团队,这能降低沟通成本;对于 AI 编程,它的价值更大。

因为如果没有接口契约:

text 复制代码
AI
 ↓
自己猜 URL
 ↓
自己猜 HTTP 方法
 ↓
自己猜参数
 ↓
自己猜返回结构
 ↓
自己猜异常处理
 ↓
项目逐渐出现多套接口风格

而建立接口规范之后:

text 复制代码
AI
 ↓
读取 API Rules
 ↓
遵循 RESTful
 ↓
复用 Schema
 ↓
复用 ApiResponse
 ↓
调用 Service
 ↓
统一异常处理

AI 不再负责"发明接口",它只负责:

按照已有接口契约完成实现。

这正是架构约束 AI 编程最重要的思想之一:

先统一规则,再扩大生成能力。

当接口路径、HTTP 方法、输入输出、状态码和异常协议都稳定下来之后,AI 写得越快,项目反而越不容易失控。

这才是 RESTful 与契约优先 真正的工程价值。

开源代码


🪐祝您好运🪐

相关推荐
heimeiyingwang1 小时前
【架构实战】分布式事务:从CAP定理到Seata实战,一文讲透跨服务数据一致性
分布式·架构
程序员-李俞1 小时前
Mistral OCR 4真正改变的不是“识字”:文档AI正在变成Agent的数据入口
人工智能·windows·ai作画·aigc·ocr·ai编程·ai写作
JavaGuide1 小时前
GitHub 4.5 万+ Star!GitNexus 把代码仓库变成了 Claude Code / Codex 能查询的知识图谱
后端·ai编程
小虎AI生活2 小时前
DeepSeek Harness 技术拆解:全插件架构、九项能力对比与本地实测
ai编程
独孤九剑打醒他2 小时前
RGB‑TOT 全架构系统仿真:红外波长簇光通信完整验证
架构
子兮曰2 小时前
DeepSeek Harness 架构深潜:一个把 Agent 运行时做成纯插件树的开源 Harness
前端·后端·deepseek
ServBay2 小时前
DeepSeek Harness 实战:如何搭建完整的 AI Agent 本地开发环境
aigc·ai编程·deepseek
这个DBA有点耶2 小时前
从库延迟的“二次放大”效应:一次大事务,拖垮整个读写分离
数据库·mysql·架构
子兮曰3 小时前
AI Agent 完整入门指南:从 LLM 到生产落地的 30+ 个核心概念
前端·后端·agent