用领域模型指导 AI 写代码:让代码仓库自己讲清业务
用领域模型指导 AI 写代码:让代码仓库自己讲清业务
想象一个刚接手项目的 AI Coding Agent。
用户只给它一句话:
会员订单的退款期限从 30 天延长到 45 天。
Agent 搜索 30,很快在 RefundService 里找到一处判断,把数字改成 45,补了一个测试,然后宣布任务完成。
代码可以编译,测试也通过了。
可真正熟悉业务的人一眼就会发现问题:只有金卡会员享受 45 天退款期,普通会员仍然是 30 天;海外订单遵循另一套政策;已经发货的订单需要先进入退货流程,不能直接退款;风控任务里还复制着另一份期限判断。
Agent 不是真的粗心。它只是从仓库里看到了一堆文件,却没有看到业务的形状。
于是我们开始给 AI 补充上下文:粘贴产品文档,解释会员等级,附上流程图,再提醒它哪些 Service 不能改。下一次换一个 Agent、换一段对话,或者上下文被压缩后,这些内容又要重新讲一遍。
更麻烦的是,那份业务文档可能写于半年前。代码已经改过三轮,文档仍然停留在旧流程。AI 面对两份互相矛盾的材料,只能猜哪一个更接近事实。
有没有一种方式,让 AI 进入代码仓库后,就能像一位熟悉业务的开发者那样,沿着清楚的概念、边界和规则找到答案?
这正是领域模型可以发挥作用的地方。
上一篇《领域模型驱动开发:让代码说业务的语言》讨论了如何把散落在文档、会议和 if/else 中的业务知识放回模型。这一篇继续往前走:当主要代码开始由 AI 阅读和修改时,领域模型不只是帮助人类设计软件,也开始成为 AI 理解业务的入口。
AI 看得懂代码,却未必看得懂业务
大模型很擅长解释一个函数。
给它几十行代码,它通常能说清输入、输出、分支和异常;给它一个报错,它也能沿着调用栈寻找原因。
真正困难的是另一个问题:
这段代码在整个业务中扮演什么角色?
普通仓库常常按技术职责组织:
controllers/
services/
repositories/
models/
jobs/
consumers/
utils/
退款逻辑可能同时出现在:
OrderController的参数校验里;RefundService的流程判断里;OrderModel的状态字段里;RetryRefundJob的失败重试里;PaymentCallbackConsumer的消息处理中;- 一段用于数据修复的 SQL 脚本里。
对人类来说,这依赖经验和记忆。老同事知道“真正的规则在 Service,Model 只是 ORM”“海外业务不要走这里”“定时任务还保留着兼容逻辑”。
AI 没有这份组织记忆。它只能依靠当前能读到的文件、符号、测试和说明,临时拼出一幅心智地图。
这里会出现四个问题。
上下文只是一张快照
AI 每次工作时看见的只是仓库的一部分。文件太多时,它必须搜索、筛选和压缩,关键规则可能根本没有进入本轮上下文。
实现细节比业务语义更显眼
update_status(4)、flag = 1、RefundServiceV2 对程序完全合法,却没有告诉 AI 这些动作在业务上意味着什么。
相同规则可能有多个版本
当规则散落在 Controller、Service 和 Job 中时,搜索结果会同时返回几份相似代码。AI 很难判断哪份才是权威实现。
文档和代码会发生漂移
文档可以解释业务,却不参与编译、测试和运行。代码修改后如果没人同步文档,两者便逐渐讲述不同的故事。
所以,问题不只是“给 AI 更多上下文”,而是:怎样让最重要的业务知识以更稳定、更容易发现、也更难过期的形式存在。
领域模型是一种可执行的语义压缩
一份五十页的退款需求文档,可能包含背景、流程、截图、会议结论和大量例子。真正影响代码的核心内容,也许只有几条:
只有已支付订单可以申请退款
普通会员退款期为 30 天
金卡会员退款期为 45 天
累计退款不能超过实付金额
已发货订单必须先进入退货流程
成功申请后产生 RefundRequested 事件
领域模型做的,不是把这几句话复制进代码注释,而是把它们变成类型、行为、不变量和测试:
class RefundPolicy:
def deadline_for(
self,
member_level: MemberLevel,
) -> timedelta:
if member_level == MemberLevel.GOLD:
return timedelta(days=45)
return timedelta(days=30)
class Order:
def request_refund(
self,
amount: Money,
member_level: MemberLevel,
policy: RefundPolicy,
requested_at: datetime,
) -> RefundRequested:
self.ensure_refundable()
self.ensure_within_refund_window(
member_level=member_level,
policy=policy,
requested_at=requested_at,
)
self.ensure_amount_is_refundable(amount)
self.status = OrderStatus.REFUNDING
return RefundRequested(self.id, amount)
当 AI 读到这段代码,它不需要先看完整产品文档,也能得到一组相当明确的结论:
- 退款不是修改字段,而是
request_refund领域行为; - 订单自己守住能否退款的条件;
- 期限由
RefundPolicy决定,不应该在 Handler 里写死; - 会员等级是规则输入,而不是散落的布尔标记;
- 成功后会产生
RefundRequested,后续流程可能依赖它。
这些知识参与编译、运行和测试。规则一旦变化,代码必须跟着变化;代码如果违背不变量,测试和模型本身可以拒绝它。
因此,可以把领域模型理解成一种可执行的语义压缩:它把庞杂业务材料中真正决定系统行为的部分,压缩成 AI 和人类都能读取的代码结构。
文档驱动依赖每轮选择和注入材料;领域模型驱动让 AI 从代码地图进入正确上下文,再用模型与测试读取当前业务事实。
这并不意味着 AI 会自动理解全部业务,也不意味着代码可以替代所有文档。但它改变了上下文的重心:AI 不再主要依赖人类每次重新讲述业务,而是先从仓库中读取一份与实现同步的业务骨架。
领域模型怎样帮助 AI 理解代码
领域模型对 AI 的帮助,不来自某一个神奇的 DDD 模式,而来自一组彼此配合的结构。
| 结构 | 给 AI 的信息 | 解决的问题 |
|---|---|---|
| 统一语言 | 稳定的业务词汇和搜索入口 | 同一概念被多个技术名称掩盖 |
| 限界上下文 | 当前概念在哪个范围内成立 | 一次把整个仓库塞进上下文 |
| 聚合与领域行为 | 合法的修改入口与不变量 | AI 直接改字段、绕过规则 |
| 值对象与 Policy | 单位、范围和可变策略 | 裸字符串、魔法数字和重复判断 |
| 领域事件 | 一次变化会影响哪些下游流程 | 只改主流程,遗漏消费者 |
| 领域测试 | 可执行的业务示例与边界 | 文档正确但代码已经漂移 |
统一语言让搜索拥有业务含义
假设用户说“延长退款期限”。
如果仓库中使用 RefundPolicy、RefundWindow、request_refund,AI 可以直接沿着这些词搜索;如果代码里只有 validate_time()、type == 3 和 process_v2(),它就必须先猜业务概念对应哪个技术符号。
好命名不仅让代码可读,也在为 AI 建立一套稳定的检索词典。
限界上下文缩小需要读取的世界
大型仓库里可能同时存在交易、库存、履约、营销和财务模型。
“订单”在交易上下文中负责价格与支付状态,在履约上下文中可能只对应一份发货任务。明确的上下文边界告诉 AI:当前需求属于哪里,哪些模块只是下游协作者,哪些同名类型不能混用。
AI 不必先读完整仓库,而可以从一个相关的限界上下文开始,逐步扩展。
聚合告诉 AI 从哪一扇门进入
如果 Order 的状态可以被任意 Service 直接修改,AI 会自然选择最短路径:找到字段,赋一个新值。
如果状态只能通过 order.request_refund()、order.cancel() 和 order.mark_as_paid() 改变,修改入口就变得明确。聚合根不仅保护业务一致性,也在向 AI 声明:
想改变订单,先理解这些领域行为。
领域事件暴露隐藏的影响范围
一个退款功能很少只修改订单。
营销系统可能要退回优惠券,库存系统可能要等待退货入库,财务系统可能要创建退款凭证。如果这些关系只藏在消息消费者和队列配置里,AI 很容易漏掉。
RefundRequested、RefundCompleted 这样的领域事件,会把“发生了什么”变成可搜索符号。AI 可以沿着事件查找生产者、消费者和跨上下文影响,进而建立更完整的变更范围。
领域测试让规则可以被反问
当 AI 不确定退款期到底是多少,它可以去看:
def test_regular_member_has_30_day_refund_window(): ...
def test_gold_member_has_45_day_refund_window(): ...
def test_shipped_order_requires_return_flow(): ...
测试不是辅助材料,而是一组可以执行的业务例句。AI 既能从名称和断言中理解规则,也能在修改后运行它们,确认自己的理解没有破坏原有事实。
清晰的仓库,才能生成清晰的代码地图
AI 第一次进入仓库时,通常会先做一件事:建立代码地图。
代码地图不应该只是文件列表。真正有用的地图需要回答:
- 仓库里有哪些业务上下文;
- 每个上下文负责什么;
- 关键聚合、命令、查询和事件在哪里;
- 一次业务行为从哪个入口进入;
- 哪些测试描述它的规则;
- 它会影响哪些外部系统或其他上下文。
如果代码结构本身围绕技术层堆放,AI 生成的地图也只能是:
controllers 有 37 个文件
services 有 82 个文件
models 有 64 个文件
utils 有 113 个文件
这是一份仓库统计,不是一张业务地图。
如果仓库先按限界上下文组织,再在上下文内部区分领域、应用和基础设施,地图会自然清楚许多:
src/
ordering/
domain/
order.py
money.py
refund_policy.py
events.py
application/
request_refund.py
get_order.py
infrastructure/
order_repository.py
payment_gateway.py
tests/
test_order_refund.py
inventory/
domain/
application/
infrastructure/
fulfillment/
domain/
application/
infrastructure/
目录形式不是关键,也不需要所有项目照抄。真正重要的是:业务边界、领域概念和依赖方向在结构中可见。
基于这样的仓库,AI 可以生成更有语义的地图:
Ordering Context
Purpose
管理订单价格、支付状态与退款申请
Aggregate
Order
behaviors: request_refund, cancel, mark_as_paid
invariants: refund limit, refund window, legal status
Policies
RefundPolicy
regular: 30 days
gold: 45 days
Commands
RequestRefund -> RequestRefundHandler -> Order.request_refund
Events
RefundRequested
consumers: payment, promotion
Tests
tests/test_order_refund.py
代码地图把业务概念映射到实现位置和影响范围;AI 用它定位入口,再按需读取聚合、事件与测试。
这张地图不需要手工复制所有代码细节。它可以通过目录、类型、导入关系、命令注册表、事件订阅和测试名称自动或半自动生成。
代码地图应该是索引,而不是第二份业务真相。它负责告诉 AI 去哪里读,真正的规则仍然由领域模型与测试表达。地图可以随代码重新生成,便不会像一份完全手写的长文档那样悄悄老去。
AI 应该怎样沿着领域模型工作
有了领域模型和代码地图,还需要给 Agent 一套稳定的阅读顺序。
面对“金卡会员退款期延长到 45 天”这个需求,一次更可靠的流程可以是:
1. 先定位限界上下文
从代码地图确认退款申请属于 Ordering Context,会员等级来自哪个上下文,以及两者通过什么快照或接口协作。
这一步可以避免 AI 因为看见 customer.level,就直接让订单模型依赖另一个上下文的内部实体。
2. 找到领域入口,而不是先找数字
先搜索 request_refund 和 RefundPolicy,理解退款行为从哪里进入、由谁守住规则;不要一上来全仓库替换 30。
3. 阅读不变量、事件和现有测试
确认:
- 订单需要处于什么状态;
- 已发货订单如何处理;
- 成功后产生什么事件;
- 普通会员和金卡会员目前有哪些测试;
- 是否有其他消费者依赖退款截止时间。
4. 用领域语言描述变更
在修改代码前,Agent 应该先形成一句清楚的变更说明:
RefundPolicy 根据 MemberLevel 计算退款期限:
REGULAR 保持 30 天,GOLD 调整为 45 天。
Order 继续只依赖 Policy 的判断,不直接编码会员规则。
如果这句话说不清楚,通常意味着模型还没有理解到可以安全修改的程度。
5. 先修改领域规则与测试
先为金卡和普通会员补充领域测试,再修改 RefundPolicy。领域模型通过后,才处理 DTO、接口或展示文案。
这可以防止 AI 在 Controller 中加一个临时分支,把相同规则再次复制出去。
6. 沿事件与调用关系检查影响
通过代码地图和符号引用检查:命令、事件消费者、API 契约和定时任务是否需要同步变化。
7. 重新生成代码地图并验证
如果新增了 Policy、行为或事件,重新生成地图;运行领域测试、架构测试和受影响上下文的集成测试。
这个流程可以写进仓库级 AGENTS.md 或 Coding Skill,但里面不需要重抄全部业务规则。它只需要告诉 AI 怎样寻找业务真相:
## Domain change workflow
1. Locate the bounded context in CODEMAP.md.
2. Read the aggregate, policy, events, and domain tests.
3. Describe the change in ubiquitous language before editing.
4. Never mutate aggregate state outside aggregate behavior.
5. Add or update domain tests before changing adapters.
6. Check event consumers and regenerate the code map.
Skill 或 AGENTS.md 负责工作方法,代码地图负责导航,领域模型负责当前业务事实,测试负责验证。
四者各自承担一层职责,就不需要把一整套退款规则同时复制到 Prompt、Skill、项目文档和代码注释中。
业务文档不会消失,只是换一个位置
“让 AI 从代码理解业务”很容易滑向另一个极端:既然代码才是事实,就不再需要业务文档。
这同样不可靠。
代码擅长表达系统现在怎样运行,却不一定能完整解释:
- 为什么公司选择 30 天而不是 15 天;
- 哪条法规要求保留这项检查;
- 某个看似多余的兼容分支服务于哪个旧客户;
- 两个上下文为什么采用异步协作;
- 这项规则的负责人是谁,何时需要重新评估。
这些“为什么”和外部约束,仍然需要文档、ADR、政策来源和决策记录。
更合理的分工是:
| 载体 | 最适合保存什么 |
|---|---|
| Domain Model | 当前合法行为、不变量和状态变化 |
| Domain Tests | 典型场景、边界条件和反例 |
| Code Map | 业务概念在哪里、怎样连接 |
| API/Event Contract | 上下文之间怎样协作 |
| ADR | 为什么选择这个模型与架构 |
| 业务/政策文档 | 规则来源、组织责任和非代码背景 |
| Skill/AGENTS.md | AI 应该按什么流程阅读和修改仓库 |
业务文档不再重复每一行可执行规则,而更像模型背后的注脚与档案;代码也不再假装能够解释所有历史和动机。
文档可以引用稳定的领域符号和测试,例如:
退款期限的当前实现:RefundPolicy
可执行示例:test_order_refund.py
政策依据:POLICY-2026-07
设计原因:ADR-018-refund-policy
这样即使业务变化,AI 也知道应该从哪里核对当前实现、从哪里理解历史原因,而不是在两份完整却互相矛盾的说明之间猜测。
领域模型也需要防止 AI 绕路
领域模型能够指导 AI,但前提是仓库真的守住这些边界。
如果 Order.status 仍然可以公开赋值,AI 在时间紧张时还是可能绕过 request_refund();如果 Job 可以直接执行 SQL,聚合里的不变量也保护不了数据库。
因此,还需要一些工程约束:
封装状态修改
让聚合内部状态只能通过领域行为改变。字段、集合和子实体不要暴露不受控的写入口。
使用架构测试
自动检查领域层不能依赖 Web、ORM 和消息框架,外部模块不能绕过聚合根访问内部实体。
让领域测试进入必跑集合
AI 完成任务前必须运行相关领域测试。高风险规则还可以加入属性测试或状态机测试。
检查直接数据写入
数据修复脚本、后台任务和消息消费者也要走合法的应用与领域入口。确实需要绕过时,应明确记录原因、范围和审计信息。
让代码地图可再生、可校验
地图从源代码和注册表生成,CI 检查它是否与当前提交一致。不要让 AI 依赖一张没人维护的旧地图。
Prompt 只能提醒 AI 不要越界,代码结构与自动化检查才能真正阻止它越界。
常见误区
目录像 DDD,代码仍然贫血
把文件放进 domain/aggregates/,却继续在 Application Service 里判断所有规则,不会让 AI 更理解业务。AI 看到的只是更漂亮的目录。
把完整业务规则复制进代码地图
地图越详细,越容易成为下一份过期文档。它应该记录概念、入口、关系和验证位置,而不是复制每个条件分支。
让 AI 独自发明领域模型
AI 可以从代码和材料中提出候选概念,却无法替代领域专家确认业务含义。一个结构优雅但理解错误的模型,会让错误更系统地扩散。
为了方便 AI,把所有东西都显式化
清晰不等于过度抽象。每条简单校验都建立一个 Policy,每个函数都产生事件,只会制造新的噪声。模型应该围绕真正的业务复杂度生长。
有了代码地图,就把整个仓库一次性加载
地图的价值是帮助按需导航。正确顺序是先定位上下文,再读取聚合、用例、事件和测试,而不是把地图指向的所有文件全部塞进上下文。
从“给 AI 讲业务”到“让仓库表达业务”
传统做法里,业务知识主要存在于人的记忆和文档里。AI 每次加入任务,团队都要临时准备一份上下文包:需求、流程、注意事项、相关文件和历史背景。
领域模型驱动的仓库,则把更多知识沉淀在可执行结构中:
统一语言让概念可搜索
限界上下文让范围可定位
聚合让修改入口可发现
值对象和 Policy 让规则可组合
领域事件让影响范围可追踪
领域测试让业务事实可验证
代码地图让整套结构可导航
此时,给 AI 的提示可以从一大段业务复述,缩短成:
在 Ordering Context 中实现会员退款期限调整。先读取代码地图、RefundPolicy、Order 聚合与退款领域测试;保持普通会员 30 天,只把金卡会员调整为 45 天,并检查 RefundRequested 的消费者。
这段提示仍然提供了当前任务的目标和边界,却不需要重新解释整个退款系统。其余知识由仓库自己回答。
小结
领域模型不会让 AI 突然拥有真正的业务经验,但它可以把业务经验变成 AI 更容易发现和验证的代码结构。
它带来的变化不是“从此不要文档”,而是重新安排知识的位置:
代码与测试保存当前可执行的业务事实
代码地图提供快速、可再生的导航
文档保存原因、来源和组织背景
Skill 告诉 AI 怎样寻找和修改这些知识
当代码仓库按业务语言组织,AI 生成的代码地图会更清楚;地图越清楚,AI 越容易进入正确的限界上下文;边界、行为和测试越明确,AI 越不容易用一个局部补丁破坏全局规则。
过去,我们努力让代码对人类可读。
现在还要再往前一步:让代码仓库能够把业务讲给 AI 听。