AI 写一个功能,通常并不难,真正难的是:

当 AI 连续写几十个、几百个功能以后,代码还能不能保持清晰?

很多项目一开始结构还不错,用户功能放一点,权限功能放一点,订单功能放一点,日志功能放一点。

随着需求越来越多,不同模块开始互相调用、互相引用、互相修改,最后可能变成这样:

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 很容易采用当前最方便的方法:

这里需要用户数据
直接调用 UserRepository

那里需要 Agent
直接查 Agent 表

这里需要权限
再加一个 if

那里需要 Tool
直接调用 ToolDatabase

慢慢地:

User
Agent
Tool
Knowledge Base
Conversation
Permission

开始互相穿插。

问题并不是 AI 写错了代码,而是:

AI 很擅长解决当前问题,却不一定天然维护整个系统的长期边界。

所以,我们需要先告诉 AI:

这是谁的地盘。


二、DDD 最值得 AI 编程借鉴的概念:边界上下文

DDD 里面有很多概念:EntityValue ObjectAggregate

对于入门读者来说,不需要一开始全部掌握。

在 AI 编程中,最值得先理解的是一个概念:

Bounded Context —— 限界上下文。

这个词听起来复杂,其实非常简单。

可以理解成:

某一类业务代码允许活动的范围。

例如一个智能体平台可能天然存在这些业务领域:

用户与权限
Agent 管理
知识库
Tool
Conversation
LLM
系统配置

我们可以把它们想象成几个房间:每个房间都可以很复杂。

但是:

复杂可以,混乱不可以。


三、架构规范:先告诉 AI 哪些代码属于哪个领域

假设我们的项目里存在:

Agent
User
Tool
Knowledge Base
Conversation

就可以先建立一些简单规则。


规则 1:按业务能力划分代码,而不是按“方便”划分

例如:

Agent 相关业务

应该尽量集中在 Agent 自己的范围内,而不是:

user.py      放一点 Agent 逻辑
tool.py      放一点 Agent 逻辑
common.py    再放一点 Agent 逻辑
utils.py     继续放一点

否则时间一长:

想修改 Agent,却不知道应该改几个文件。


规则 2:一个领域不要随意操作另一个领域的数据

例如 Conversation 需要知道:

当前对话属于哪个 Agent。

它可以保存:

agent_id

但这并不意味着 Conversation 模块就应该随意修改 Agent 的内部状态。

更好的思路是:

Conversation
     │ 需要 Agent 能力
Agent Service / Public Interface

而不是:

Conversation
直接操作 Agent 内部数据库

规则 3:跨领域协作要经过明确接口

如果 Agent 需要 Tool:

不要让 Agent 到处直接操作 Tool 数据表,而应该有明确关系:

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 可能在整个项目搜索,有明确边界以后,它首先应该关注:

Agent API
Agent Service
Agent Repository
Agent Schema

也就是:

把一个全局问题,缩小成一个局部问题。


2. AI 不容易到处“顺手改代码”

AI 有一个非常常见的倾向:

既然这个地方也能解决,那就顺手改一下。

一次看不出问题,几十次以后,边界就消失了。

如果规则明确:

Conversation 逻辑只能修改 Conversation 相关代码,除非确实需要跨领域协作。

AI 的修改范围就更可控。


3. 修改一个领域,不容易误伤整个系统

例如以后重构知识库。

如果 Knowledge Base 有比较清晰的边界:

Knowledge Base
├── API
├── Service
├── Retrieval
├── Repository
└── Storage

那么重构知识库时,大部分工作都可以控制在这个范围。而不是一修改:

Agent 崩了
Conversation 崩了
User 也崩了

4. 新人也更容易理解项目

这不仅对 AI 有好处,对人也一样。

一个新开发者进入项目时,如果看到:

agent
knowledge_base
tool
conversation
user

就可以快速建立认知:

原来系统主要由这些业务能力组成。

而不是先研究几百个文件才能知道项目到底是干什么的。


5. 项目更适合长期交给 AI 维护

AI 编程最怕的不是第一次生成代码不好,而是:

第一次 AI 这样写
第二次 AI 那样写
第三次又换一种结构
第四次开始互相引用

最后整个项目没有稳定形态。

领域边界相当于持续告诉 AI:

你可以改,但不要突破边界。


五、提示词落地:把领域边界真正告诉 AI

因此,不能只告诉 AI:

本项目采用 DDD。

这句话几乎没有实际约束力,更有效的方式,是写成可以执行的规则。

例如:

## 领域边界

系统围绕业务领域构建。主要领域包括:

- User & Permission
- Agent
- Knowledge Base
- Tool
- Conversation
- LLM
- System Configuration

规则:

- 将业务逻辑保留在其所属领域内。
- 不要将领域逻辑放在通用工具或不相关的模块中。
- 除非现有架构明确允许,否则不要直接修改其他领域的内部数据。
- 跨领域操作应通过现有的服务、存储库、工厂或已定义的接口进行。
- 重用现有领域术语。
- 不要为现有领域对象创建名称不同的替代概念。
- 在实现某个功能之前,请确定它属于哪个领域。
- 尽可能将更改保留在该领域内。

以后再让 AI 开发功能时,就不要只是:

给 Agent 增加 Tool 绑定功能。

而是:

给 Agent 增加 Tool 绑定功能。

先确认该功能属于 Agent 领域。

遵守现有 Agent、Tool 的领域边界,优先复用现有 Service、Repository 和关系模型。

不要把业务逻辑写进 API Router,不要直接在 Router 中操作数据库。

这时 AI 得到的不只是:

做什么。

还有:

在哪个上下文里做。


六、正面产出:看看 miniagent 是怎么划边界的

以实际项目 miniagent 为例。

flowchart TB UI["前端
Management / Workplace"] API["API 层
app/api"] subgraph DOMAIN["业务领域 / 限界上下文"] USER["用户与权限领域
User / Role / Permission"] AGENT["Agent 领域
Agent / Agent-Tool / Agent-User"] KB["知识库领域
Knowledge Base / Document / Retrieval"] TOOL["工具领域
Tool / Web Search / SQL Agent"] CONV["会话领域
Conversation / Session / Message"] MODEL["模型领域
LLM / Embedding / Router Config"] end SERVICE["业务服务层
app/services"] RUNTIME["运行时能力
app/runtime
AgentRunner / Retrieval / LLM"] REPO["数据访问层
app/repositories"] INFRA["基础设施层
app/infra"] DATA["数据与外部资源
SQLite / DuckDB / ChromaDB / BM25 / Files / LLM APIs"] UI -->|"REST / SSE"| API API --> SERVICE SERVICE --> USER SERVICE --> AGENT SERVICE --> KB SERVICE --> TOOL SERVICE --> CONV SERVICE --> MODEL AGENT -->|"绑定 / 协作"| TOOL AGENT -->|"授权关系"| USER AGENT -->|"检索能力"| KB AGENT -->|"使用模型"| MODEL CONV -->|"运行 Agent"| AGENT SERVICE --> RUNTIME SERVICE --> REPO RUNTIME --> REPO REPO --> INFRA RUNTIME --> INFRA INFRA --> DATA

miniagent 当前并不是一个完全按照教科书实现的 DDD 项目,但是,它已经有一个对 AI 编程非常重要的特点:

系统中的核心业务能力已经被清晰地识别出来。

miniagent 的核心能力包括:

Agent
Model
Knowledge Base
Tool
SQL Agent
Permission
Conversation
System Configuration

后端又进一步划分为:

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 文件是:

app/api/admin/agent.py

Service 是:

app/services/admin/agent.py

API 文件自己就写得非常明确:

# Agent API Router – HTTP layer only,
# all logic lives in AgentService

也就是说:

Router 只负责 HTTP,业务逻辑属于 AgentService。

例如创建 Agent:

@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 没有:

直接 INSERT 数据库
自己判断缓存
自己维护 Tool

而是把 Agent 业务交给:

AgentService

2. AgentService 负责 Agent 自己的业务

AgentService 中,代码明确写着:

class AgentService:
    """
    Encapsulates all business logic for the Agent resource.
    """

也就是:

Agent 的业务逻辑集中在 AgentService。

例如修改 Agent:

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)

这里可以看到:

Agent 修改
Agent 数据访问
Agent 缓存失效
返回 Agent 模型

这些都由 AgentService 组织。


3. 跨领域协作也有明确入口

Agent 不可能永远只和自己打交道。

例如一个 Agent 可能:

绑定 User
绑定 Tool
绑定 LLM

这正好是领域边界最有意思的地方:miniagent 并没有因此把所有数据逻辑塞进 API。

例如绑定 Tool 时:

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 非常简单:

@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()

从阅读者角度看,这段代码非常容易理解:

收到请求
检查权限
交给 AgentService
返回结果

这就是边界带来的价值。


七、对 AI 来说,这种项目结构意味着什么?

现在假设我们告诉 AI:

给 Agent 增加一个新的配置字段。

AI 进入 miniagent 后,可以沿着非常清晰的路径寻找:

Agent Schema
Agent API
Agent Service
Agent Database / Repository

如果需求是:

修改知识库检索逻辑。

它应该关注:

Knowledge Base
Retrieval Pipeline
Vector Store
BM25

而不是跑去修改 Agent 管理页面或者 User 权限代码。

于是整个项目逐渐形成:

需求
识别所属领域
进入对应上下文
按照已有分层修改
必要时通过明确接口跨领域协作

这其实就是 DDD 对 AI 编程非常现实的价值。


八、不要把 DDD 理解成“多建几个文件夹”

这里有一个非常容易出现的误区。

有人认为:

user/
agent/
tool/
knowledge_base/

创建几个目录,就叫 DDD。

当然不是,真正关键的是:

这些目录背后有没有稳定的业务职责。

如果:

AgentService

什么都干:

用户
权限
知识库
Tool
日志
邮件
系统配置

那即使目录名字再漂亮,也仍然可能是“大泥球”。

所以真正重要的是:

一个业务概念
明确的职责
明确的边界
明确的协作方式

结语

DDD 是一个很大的软件设计体系。但对于 AI 编程来说,我们并不需要一开始就掌握所有:

Entity
Value Object
Aggregate
Domain Event
Domain Service
Repository

更重要的第一步其实只有一个:

先告诉 AI,这个系统由哪些业务领域组成。

然后进一步告诉它:

你现在正在修改哪个领域,以及哪些地方不要碰。

这就像给 AI 一张城市地图。没有地图的时候:

AI 哪里能走就往哪里走。

有了领域边界以后:

User 是一个区域
Agent 是一个区域
Knowledge Base 是一个区域
Tool 是一个区域
Conversation 是一个区域

跨区域当然不是禁止的,但必须走“正规道路”。

于是:

DDD 对 AI 最大的价值,不是增加设计复杂度,而是减少代码世界里的无序自由。

当 AI 每天可以生成几千甚至几万行代码时,这种边界会变得越来越重要。

先划定领域,再让 AI 在领域内解决问题。

这正是避免 AI 把大型项目逐渐写成“大泥球”的重要方法。

开源代码


🪐祝您好运🪐