领域驱动设计:给 AI 划定上下文边界,告别“大泥球”代码

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 里面有很多概念:EntityValue ObjectAggregate...

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

在 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 为例。

flowchart TB UI["前端<br/>Management / Workplace"] API["API 层<br/>app/api"] subgraph DOMAIN["业务领域 / 限界上下文"] USER["用户与权限领域<br/>User / Role / Permission"] AGENT["Agent 领域<br/>Agent / Agent-Tool / Agent-User"] KB["知识库领域<br/>Knowledge Base / Document / Retrieval"] TOOL["工具领域<br/>Tool / Web Search / SQL Agent"] CONV["会话领域<br/>Conversation / Session / Message"] MODEL["模型领域<br/>LLM / Embedding / Router Config"] end SERVICE["业务服务层<br/>app/services"] RUNTIME["运行时能力<br/>app/runtime<br/>AgentRunner / Retrieval / LLM"] REPO["数据访问层<br/>app/repositories"] INFRA["基础设施层<br/>app/infra"] DATA["数据与外部资源<br/>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 的核心能力包括:

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 把大型项目逐渐写成"大泥球"的重要方法。

开源代码


🪐祝您好运🪐

相关推荐
这token有力气1 小时前
3000行全塞一个 index.html?四刀瘦身手术,不装 Vite 也能模块化
架构
岛雨QA1 小时前
Claude Code国内无障碍接入 DeepSeek使用指南
ai编程·claude·deepseek
用户6919026813392 小时前
用浏览器 WebGPU跑DPSK大模型(1) - 模型的下载和前端下载进度的显示
javascript·react.js·架构
xiaoshuai10242 小时前
编译管不着的跨层 bug:用 4 道脚本闸守住 API/SQL/权限/迁移
架构
ServBay3 小时前
DeepSeek V4 Pro 发布,1.6T 参数、1M 上下文,又有人坐不住了
aigc·ai编程·deepseek
Dr.kangder3 小时前
嵌入式面试总结(八)——大小端
嵌入式硬件·面试·职场和发展·架构·嵌入式
李白客3 小时前
分布式集群与数据库产业:从单机到集群的架构跃迁与市场重构
数据库·分布式·架构
画中有画4 小时前
Kappa 架构在大数据实时处理系统中的应用
大数据·架构
坚强小橙4 小时前
30 分钟的重复工作,我写了 Claude Code Skill 变成了 8 秒
ai编程