AI 写一个功能,通常并不难,真正难的是:
当 AI 连续写几十个、几百个功能以后,代码还能不能保持清晰?
很多项目一开始结构还不错,用户功能放一点,权限功能放一点,订单功能放一点,日志功能放一点。
随着需求越来越多,不同模块开始互相调用、互相引用、互相修改,最后可能变成这样:
text
user.py
↓
permission.py
↓
agent.py
↓
tool.py
↓
knowledge_base.py
↓
conversation.py
↘
又调用 user.py
文件很多,代码也不少,但谁负责什么,已经越来越说不清。
软件架构里有一个非常形象的名字:
大泥球(Big Ball of Mud)
而领域驱动设计,也就是我们经常听到的 DDD,其中一个非常重要的作用,就是在系统开始变乱之前,先把边界划清楚。
对于 AI 编程来说,这个作用尤其重要。
📌 技术名片
领域驱动设计 / Domain-Driven Design,简称 DDD
DDD 是一种围绕业务领域组织软件的方法。
它强调先理解"系统里有哪些不同业务",再为这些业务划定边界,让不同领域的代码各自负责自己的事情。
💡 一句话理解
可以把一个大型软件系统想象成一家大型医院。医院里有:挂号、门诊、药房...
这些部门都属于同一家医院,但是我们不会因为它们都属于医院,就让:
药房直接修改财务系统数据库,
检验科直接处理医生排班,
收费系统直接修改病历。
每个部门都有自己的职责,需要协作时,通过明确的流程进行。
DDD 做的事情非常类似:
先把软件里的"业务部门"划出来,再规定每个部门负责什么。
一、为什么 AI 特别容易写出"大泥球"代码?
这其实和 AI 的工作方式有关,AI 接到的通常是一个个局部任务:
增加用户查询功能。
过一会儿:
给 Agent 增加 Tool。
再过一会儿:
用户只能看到自己有权限使用的 Agent。
然后:
对话记录需要关联 Agent。
每一个任务单独看都很合理,但 AI 很容易采用当前最方便的方法:
text
这里需要用户数据
↓
直接调用 UserRepository
那里需要 Agent
↓
直接查 Agent 表
这里需要权限
↓
再加一个 if
那里需要 Tool
↓
直接调用 ToolDatabase
慢慢地:
text
User
Agent
Tool
Knowledge Base
Conversation
Permission
开始互相穿插。
问题并不是 AI 写错了代码,而是:
AI 很擅长解决当前问题,却不一定天然维护整个系统的长期边界。
所以,我们需要先告诉 AI:
这是谁的地盘。
二、DDD 最值得 AI 编程借鉴的概念:边界上下文
DDD 里面有很多概念:Entity、Value Object、Aggregate...
对于入门读者来说,不需要一开始全部掌握。
在 AI 编程中,最值得先理解的是一个概念:
Bounded Context ------ 限界上下文。
这个词听起来复杂,其实非常简单。
可以理解成:
某一类业务代码允许活动的范围。
例如一个智能体平台可能天然存在这些业务领域:
text
用户与权限
Agent 管理
知识库
Tool
Conversation
LLM
系统配置
我们可以把它们想象成几个房间:每个房间都可以很复杂。
但是:
复杂可以,混乱不可以。
三、架构规范:先告诉 AI 哪些代码属于哪个领域
假设我们的项目里存在:
text
Agent
User
Tool
Knowledge Base
Conversation
就可以先建立一些简单规则。
规则 1:按业务能力划分代码,而不是按"方便"划分
例如:
text
Agent 相关业务
应该尽量集中在 Agent 自己的范围内,而不是:
text
user.py 放一点 Agent 逻辑
tool.py 放一点 Agent 逻辑
common.py 再放一点 Agent 逻辑
utils.py 继续放一点
否则时间一长:
想修改 Agent,却不知道应该改几个文件。
规则 2:一个领域不要随意操作另一个领域的数据
例如 Conversation 需要知道:
当前对话属于哪个 Agent。
它可以保存:
text
agent_id
但这并不意味着 Conversation 模块就应该随意修改 Agent 的内部状态。
更好的思路是:
text
Conversation
│
│ 需要 Agent 能力
▼
Agent Service / Public Interface
而不是:
text
Conversation
↓
直接操作 Agent 内部数据库
规则 3:跨领域协作要经过明确接口
如果 Agent 需要 Tool:
不要让 Agent 到处直接操作 Tool 数据表,而应该有明确关系:
text
Agent
↓
Tool Service / Repository
↓
Tool
这样 AI 能知道:
"我现在正在处理 Agent 业务,如果需要 Tool,应该通过已有能力访问,而不是随便改 Tool 内部实现。"
规则 4:领域名称要保持一致
这一点对 AI 特别重要。
例如项目里已经叫 Agent,那就不要今天叫 Bot ,明天 AI 又创建 Assistant ,后天再出现 AIWorker ...
DDD 非常强调一种东西:
统一语言。
也就是团队、代码、数据库、接口尽量使用同一套业务词汇。
对于 AI 来说,这相当于减少歧义。
四、这样做有什么好处?
DDD 对 AI 编程的好处,并不只是"目录看起来整齐"。
它真正解决的是:
AI 每次修改代码时,到底应该改到哪里。
1. AI 的搜索范围变小了
假设用户说:
给 Agent 增加一个启用/停用功能。
没有领域边界时,AI 可能在整个项目搜索,有明确边界以后,它首先应该关注:
text
Agent API
Agent Service
Agent Repository
Agent Schema
也就是:
把一个全局问题,缩小成一个局部问题。
2. AI 不容易到处"顺手改代码"
AI 有一个非常常见的倾向:
既然这个地方也能解决,那就顺手改一下。
一次看不出问题,几十次以后,边界就消失了。
如果规则明确:
Conversation 逻辑只能修改 Conversation 相关代码,除非确实需要跨领域协作。
AI 的修改范围就更可控。
3. 修改一个领域,不容易误伤整个系统
例如以后重构知识库。
如果 Knowledge Base 有比较清晰的边界:
text
Knowledge Base
├── API
├── Service
├── Retrieval
├── Repository
└── Storage
那么重构知识库时,大部分工作都可以控制在这个范围。而不是一修改:
text
Agent 崩了
Conversation 崩了
User 也崩了
4. 新人也更容易理解项目
这不仅对 AI 有好处,对人也一样。
一个新开发者进入项目时,如果看到:
text
agent
knowledge_base
tool
conversation
user
就可以快速建立认知:
原来系统主要由这些业务能力组成。
而不是先研究几百个文件才能知道项目到底是干什么的。
5. 项目更适合长期交给 AI 维护
AI 编程最怕的不是第一次生成代码不好,而是:
text
第一次 AI 这样写
第二次 AI 那样写
第三次又换一种结构
第四次开始互相引用
最后整个项目没有稳定形态。
领域边界相当于持续告诉 AI:
你可以改,但不要突破边界。
五、AI 失控现场:没有领域边界会发生什么?
假设我们让 AI 实现:
给 Agent 绑定多个 Tool。
如果项目没有领域边界,AI 可能直接在接口层做全部事情:
python
@router.put("/agent/{agent_id}/tools")
async def update_tools(agent_id, tool_ids):
agent = await db.get_agent(agent_id)
tools = await db.query_tools(tool_ids)
await db.delete_agent_tools(agent_id)
for tool in tools:
await db.insert_agent_tool(agent_id, tool.id)
cache.clear()
return {"success": True}
功能可能真的能跑,但这里已经混合了:
text
HTTP 接口
Agent 业务
Tool 校验
关系维护
数据库操作
缓存失效
下一次 AI 再开发:
给 Agent 绑定用户。
很可能再复制一套类似逻辑。然后:
Agent 绑定 LLM。
再来一次...
最后 API 文件越来越大,Service 没人用了,Repository 到处被直接调用。
整个系统逐渐变成:
功能都能跑,但谁负责什么已经说不清。
这就是"大泥球"。
六、提示词落地:把领域边界真正告诉 AI
因此,不能只告诉 AI:
本项目采用 DDD。
这句话几乎没有实际约束力,更有效的方式,是写成可以执行的规则。
例如:
text
## 领域边界
系统围绕业务领域构建。主要领域包括:
- User & Permission
- Agent
- Knowledge Base
- Tool
- Conversation
- LLM
- System Configuration
规则:
- 将业务逻辑保留在其所属领域内。
- 不要将领域逻辑放在通用工具或不相关的模块中。
- 除非现有架构明确允许,否则不要直接修改其他领域的内部数据。
- 跨领域操作应通过现有的服务、存储库、工厂或已定义的接口进行。
- 重用现有领域术语。
- 不要为现有领域对象创建名称不同的替代概念。
- 在实现某个功能之前,请确定它属于哪个领域。
- 尽可能将更改保留在该领域内。
以后再让 AI 开发功能时,就不要只是:
text
给 Agent 增加 Tool 绑定功能。
而是:
text
给 Agent 增加 Tool 绑定功能。
先确认该功能属于 Agent 领域。
遵守现有 Agent、Tool 的领域边界,优先复用现有 Service、Repository 和关系模型。
不要把业务逻辑写进 API Router,不要直接在 Router 中操作数据库。
这时 AI 得到的不只是:
做什么。
还有:
在哪个上下文里做。
七、正面产出:看看 miniagent 是怎么划边界的
以实际项目 miniagent 为例。
miniagent 当前并不是一个完全按照教科书实现的 DDD 项目,但是,它已经有一个对 AI 编程非常重要的特点:
系统中的核心业务能力已经被清晰地识别出来。
miniagent 的核心能力包括:
text
Agent
Model
Knowledge Base
Tool
SQL Agent
Permission
Conversation
System Configuration
后端又进一步划分为:
text
app/api/
app/services/
app/runtime/
app/repositories/
app/schemas/
app/infra/
其中:
api负责 HTTP 路由;services负责业务逻辑;runtime负责 Agent、Session、LLM、Retrieval 等运行时组件;repositories负责异步数据访问;schemas负责数据模型;infra负责数据库、缓存和基础设施。
这实际上已经给 AI 建立了两种边界:业务边界 和 技术层边界。
1. Agent 是一个明确的业务上下文
以 Agent 管理为例。
miniagent 的 API 文件是:
text
app/api/admin/agent.py
Service 是:
text
app/services/admin/agent.py
API 文件自己就写得非常明确:
python
# Agent API Router -- HTTP layer only,
# all logic lives in AgentService
也就是说:
Router 只负责 HTTP,业务逻辑属于 AgentService。
例如创建 Agent:
python
@router.post("")
async def create_agent(
payload: AgentCreate,
svc: AgentService = Depends(get_service),
caller_id: int = Depends(_add),
):
agent_out = await svc.create_agent(payload)
return ApiResponse(data=agent_out)
Router 没有:
text
直接 INSERT 数据库
自己判断缓存
自己维护 Tool
而是把 Agent 业务交给:
text
AgentService
2. AgentService 负责 Agent 自己的业务
在 AgentService 中,代码明确写着:
python
class AgentService:
"""
Encapsulates all business logic for the Agent resource.
"""
也就是:
Agent 的业务逻辑集中在 AgentService。
例如修改 Agent:
python
async def update_agent(
self,
agent_id: int,
payload: AgentUpdate
) -> AgentOut:
agent = await self._agent_db.update_agent(
agent_id,
payload.model_dump(exclude_unset=True)
)
if agent is None:
raise AgentNotFoundError(agent_id)
updated = await self._agent_db.get_agent(agent_id)
self._cache.on_agent_changed(agent_id)
return AgentOut.model_validate(updated)
这里可以看到:
text
Agent 修改
↓
Agent 数据访问
↓
Agent 缓存失效
↓
返回 Agent 模型
这些都由 AgentService 组织。
3. 跨领域协作也有明确入口
Agent 不可能永远只和自己打交道。
例如一个 Agent 可能:
text
绑定 User
绑定 Tool
绑定 LLM
这正好是领域边界最有意思的地方:miniagent 并没有因此把所有数据逻辑塞进 API。
例如绑定 Tool 时:
python
async def update_agent_tools(
self,
agent_id: int,
tool_ids: list[int]
) -> None:
unique_tool_ids = list(dict.fromkeys(tool_ids))
tools = await self._tool_db.get_tools_by_ids(
unique_tool_ids
)
found_tool_ids = {tool.id for tool in tools}
missing_tool_ids = [
tool_id
for tool_id in unique_tool_ids
if tool_id not in found_tool_ids
]
if missing_tool_ids:
raise ToolNotFoundError(missing_tool_ids)
await self._agent_tool_relation_db.update_agent_tools(
agent_id,
unique_tool_ids,
)
self._cache.on_agent_changed(agent_id)
这里已经体现出了一个非常重要的思想:
Agent 负责组织"给 Agent 绑定 Tool"这个业务动作。
判断Tool 是否存在、Agent 与 Tool 关系的更新、更新缓存,这些事情集中在 Agent 业务入口里,而不是散落到页面、Router 和数据库模型中。
4. API 只表达业务动作
对应的 HTTP API 非常简单:
python
@router.put("/{agent_id}/tools")
async def update_agent_tools(
agent_id: int,
data: AgentToolUpdate,
svc: AgentService = Depends(get_service),
caller_id: int = Depends(_edit),
):
await svc.update_agent_tools(
agent_id,
data.tool_ids
)
return ApiResponse()
从阅读者角度看,这段代码非常容易理解:
text
收到请求
↓
检查权限
↓
交给 AgentService
↓
返回结果
这就是边界带来的价值。
八、对 AI 来说,这种项目结构意味着什么?
现在假设我们告诉 AI:
给 Agent 增加一个新的配置字段。
AI 进入 miniagent 后,可以沿着非常清晰的路径寻找:
text
Agent Schema
↓
Agent API
↓
Agent Service
↓
Agent Database / Repository
如果需求是:
修改知识库检索逻辑。
它应该关注:
text
Knowledge Base
Retrieval Pipeline
Vector Store
BM25
而不是跑去修改 Agent 管理页面或者 User 权限代码。
于是整个项目逐渐形成:
text
需求
↓
识别所属领域
↓
进入对应上下文
↓
按照已有分层修改
↓
必要时通过明确接口跨领域协作
这其实就是 DDD 对 AI 编程非常现实的价值。
九、不要把 DDD 理解成"多建几个文件夹"
这里有一个非常容易出现的误区。
有人认为:
text
user/
agent/
tool/
knowledge_base/
创建几个目录,就叫 DDD。
当然不是,真正关键的是:
这些目录背后有没有稳定的业务职责。
如果:
text
AgentService
什么都干:
text
用户
权限
知识库
Tool
日志
邮件
系统配置
那即使目录名字再漂亮,也仍然可能是"大泥球"。
所以真正重要的是:
text
一个业务概念
↓
明确的职责
↓
明确的边界
↓
明确的协作方式
结语
DDD 是一个很大的软件设计体系。但对于 AI 编程来说,我们并不需要一开始就掌握所有:
text
Entity
Value Object
Aggregate
Domain Event
Domain Service
Repository
更重要的第一步其实只有一个:
先告诉 AI,这个系统由哪些业务领域组成。
然后进一步告诉它:
你现在正在修改哪个领域,以及哪些地方不要碰。
这就像给 AI 一张城市地图。没有地图的时候:
AI 哪里能走就往哪里走。
有了领域边界以后:
text
User 是一个区域
Agent 是一个区域
Knowledge Base 是一个区域
Tool 是一个区域
Conversation 是一个区域
跨区域当然不是禁止的,但必须走"正规道路"。
于是:
DDD 对 AI 最大的价值,不是增加设计复杂度,而是减少代码世界里的无序自由。
当 AI 每天可以生成几千甚至几万行代码时,这种边界会变得越来越重要。
先划定领域,再让 AI 在领域内解决问题。
这正是避免 AI 把大型项目逐渐写成"大泥球"的重要方法。
开源代码
🪐祝您好运🪐