去年我们接到一个挺棘手的任务:重构一套每天高峰要扛几十万级并发请求、但代码还停留在事务脚本阶段的闪购系统。当时我对DDD能不能在这种老系统上落地,心里是没底的——不是概念难,而是老系统的业务逻辑早散得到处都是,靠人工梳理,工期根本排不开。后来我们换了个思路:把AI当成“建模速记员”和“代码生成工人”,把团队里的架构师和领域专家拉回到建模本身。重构结束后的结果,就是标题里那两个数字:核心链路代码量减少52%,单需求平均交付成本下降超过75%。这篇把整套打法完整拆开,从为什么选DDD、AI在建模环节怎么用,到代码生成和量化口径,最后是我们踩过的坑,尽量讲透。需要先说明的是,为避免不必要的品牌指向,下文所有业务描述都以“某电商平台的闪购业务系统”为背景。这套方法的适用面远不止电商,凡是存在大量业务规则、多人协作、长期演进的中后台系统,思路都可以平移。1. 为什么说老系统改不动,才轮到DDD出手1.1 一个典型闪购模块的事务脚本长什么样几乎所有“改不动”的系统,病灶都很相似。拿闪购最常见的下单场景来说,传统代码往往是一个巨大的下单Service,里面依次塞了几十个步骤:校验活动、校验用户资格、查询库存、扣减库存、计算优惠、校验优惠、创建订单、发送消息……每一步都直接操作数据库,中间夹杂着一堆if else。业务上明明有“闪购活动”“库存”“订单”“优惠券”这些清晰概念,代码里却被压扁成一张“流水线表”。更麻烦的是,订单状态直接散落在多个Service里,支付回调改一下订单状态,超时任务也改一下订单状态,活动减库存和售后加库存也各写各的。久而久之,“订单状态机”成了薛定谔的猫——谁都在摸,谁都不知道全貌。这种代码其实有个专门的名字:贫血模型加事务脚本。它最大的问题不是行数多,而是“领域逻辑”和“技术实现”完全搅在一起。改一个优惠规则,可能要联动下单、支付、对账三个服务;加一个新活动类型,几乎要把整条链路重新走一遍。1.2 没有领域边界的代价:改一个需求要多花多少成本我们重构前做过一次粗略统计:过去一年团队60%的迭代都集中在下单与促销模块,而这两个模块的代码耦合度最高。一个典型的“延长闪购活动时间”需求,放在老系统里要动7个接口、20多处状态判断,联调时谁都不敢保证没漏。这里最耗时的不是写代码,而是开会。业务同学说“活动快照”,开发理解成“把活动配置复制一份”;业务说“超时取消”,开发理解成“把订单状态改成已取消”。术语不一致,代码里的命名跟着漂移——同一个概念在不同服务里有三个名字。这就是典型的缺“统一语言”(Ubiquitous Language)。DDD的核心理念说白了就两件事:第一,先把业务的边界划清楚,每个领域模型只关心自己的事;第二,用统一的业务语言沟通,代码命名跟着业务术语走,而不是跟着数据库表名走。1.3 AI在重构里的真实定位:不是替代架构师,而是放大产能很多文章把“AI重构”写得像魔法:丢一个旧项目进去,AI自动吐出漂亮整洁的新代码。实际完全不是这样。我们尝试过让AI直接“翻译”老代码,结果它把事务脚本里的坏味道原封不动搬到了新代码里——因为老代码本身就是一种不太合理的表达,AI照着学,只能学到错误范式。真正的分工应该是:人负责“知道业务到底想要什么”,AI负责“把已经想清楚的业务翻译成高质量代码”。说得再直白一点,DDD的建模过程特别适合AI参与,因为建模需要大量枚举事件、整理词汇、拆分工序,这些是LLM的强项;而最终的边界划分、聚合设计、业务规则取舍,仍然要人来拍板。我们团队当时有个判断:如果硬靠人工做事件风暴和代码迁移,至少得8周;AI只能帮我们压缩工期,不能帮我们做决策。实际干下来,这个判断是对的。AI把我们从大量机械劳动里解放出来,让我们有更多时间讨论真正的业务问题。2. 事件风暴先行:AI怎样把业务规则变成统一语言2.1 事件风暴不是“开会”,而是一条事件时间轴大多数团队对事件风暴的理解容易跑偏,觉得就是邀请业务专家和技术人员坐在一起贴便签纸。其实事件风暴的核心产出物,是一条按时间顺序排列的事件流。比如闪购下单链路,我们梳理出这样一串核心事件:用户提交下单请求系统校验闪购活动资格系统锁定活动库存系统计算并应用优惠订单状态变为已提交支付结果通知到达订单状态变为已支付用户未支付,系统触发超时取消库存回补每个事件都对应一个明确的业务结果。这么做最大的价值,是把“业务发生了什么”和“系统要做什么”分开了。事件说出来,业务专家听得懂;落到代码里,事件就是领域事件消息,结构也自然清晰。2.2 用AI做流程文本清洗和事件提取我们手头有一大堆老接口文档、历史需求单、会议纪要,全是口语化的描述。比如“用户下单的时候如果这个券已经过期了,那就不能用,还要给提示”。这些话句式混乱、术语不统一,靠人读非常低效。我的做法是把这些原始文本整理后扔给AI,让它先做“事件提取”,提示词大致长这样:下面是一段闪购下单流程的业务描述,请提取出: 1. 所有业务事件(用“xxx已完成/已拒绝”的过去时命名) 2. 触发这些事件的命令或动作 3. 每个事件发生时需要校验的业务规则 4. 涉及的实体名词 要求:统一名词,不要出现“订单状态改为”这类技术表述,改用业务事件描述,例如“订单已提交”。原始描述如下: ……这一步产出的候选事件可能有一百多个,有一定重复和噪音,但没关系。AI负责把覆盖面铺开,人工负责收敛和纠正。实测下来,AI提取出的候选事件覆盖了老系统80%以上的行为,剩下20%藏在代码逻辑里或者只在业务口头流传,必须靠领域专家补充。2.3 提示词里最重要的三个约束:统一语言、事件命名规范、命令与事件的区分用AI做事件提取,提示词里的约束比问题本身更重要。我们迭代了很多轮,最终沉淀出三个关键约束:第一,强制统一语言。业务同学可能一会儿说“过期”、一会儿说“失效”,但代码里只能选一个词。我们在提示词里明确:“以下每个概念只能有一个标准名称,请先给出一份术语对照表”。第二,强制事件采用过去时命名。因为事件代表已经发生的事实,用“订单已取消”而不是“取消订单”。这个约束能让后续领域事件的建模一致得多。第三,强制区分“命令”和“事件”。命令是外部请求,比如“用户提交订单”;事件是系统内部产生的已发生事实,比如“订单已提交”。AI如果不区分,很容易把命令和事件混在一起,导致领域模型语义混乱。这里分享一个我们踩过的小坑:有一次为了省事,没在提示词里约定事件命名,AI生成了“订单超时取消”“订单支付成功”这样的中英混杂命名,看起来没问题,但后面做事件溯源时发现,消息主题名称和事件类名对不上,排查了很久。统一命名规范这件事,越早定越好。2.4 建模产物如何反向校验业务:用“如果”测试逼问边界事件风暴产出事件轴以后,不能直接进编码,还要做一轮边界校验。我习惯用“如果……会怎样”来逼问模型:如果用户下单时活动库存只剩1件,但用户同时下了2件,系统怎么处理?如果用户支付时优惠券已经过期,事件顺序应该是“优惠失效”还是“支付失败”?如果支付回调延迟了2小时,超时取消任务和支付回调谁先到?这些场景AI是没法代替业务判断的,但可以用AI帮忙列候选场景。做法是把已经梳理好的事件轴和聚合边界发给AI,让它扮演“毒舌测试员”,列出所有可能的边界场景清单,再让业务专家逐个确认。这一步看起来不起眼,却直接决定聚合设计是否可靠。因为聚合的本质就是“保证不变量”的边界——比如库存聚合要保证“不能超卖”,优惠聚合要保证“一张券同一个订单只能用一次”。边界没划清楚,后面生成代码越快,返工越多。3. 让AI生成领域层代码:提示词、脚手架与三层验证3.1 从事件轴到聚合设计:把“锁库存”变成领域服务事件轴定完之后,下一步是把事件归类到聚合。例如“库存锁定”“库存回补”这些事件,加上“查询剩余库存”“预占库存”这些命令,显然应该聚到同一个库存聚合里;优惠相关的事件聚到优惠券聚合;订单状态变迁聚到订单聚合。聚合的粒度不能太小,否则又退回事务脚本;也不能太大,否则聚合内并发冲突严重。我们的经验是,一次购物车结算涉及到的库存预占、优惠计算、订单生成,分别拆成三个聚合,通过领域事件解耦,而不是把三个聚合塞进一个“大结算根”。这一步设计好了,AI生成代码的“输入”就齐了。我们把聚合清单、事件清单、命令清单、业务规则清单打包成一份结构化的“建模契约”,作为后续所有生成提示词的锚点。3.2 提示词工程模板:让AI输出DDD分层代码以下是我们实际使用过、效果还不错的生成提示词模板,核心思路是“先给上下文,再给任务,最后给约束”。你可以直接复制改造:你是一位资深DDD技术专家。下面是一个“库存聚合”的建模契约: 聚合名称:库存 核心实体:sku库存 不变量:闪购库存不允许超卖 领域命令: - 预占库存(请求携带skuId、数量、活动Id) - 释放预占(请求携带预占单号) 领域事件: - 库存已预占(事件携带预占单号、skuId、数量) - 库存已释放(事件携带预占单号、skuId、数量) 仓储接口: - 按skuId查找可用库存 - 保存预占记录 请按端口-适配器架构生成Java代码,要求: 1. 领域层不依赖Spring、不依赖数据库; 2. 聚合内部的业务规则写在领域服务中,而不是写在应用服务中; 3. 外部输入通过命令对象传入,所有校验失败抛出明确业务异常; 4. 返回结果使用领域事件+返回值,不要直接在聚合里发消息。根据这个模板生成出的聚合根代码,结构基本符合预期。下面是一段浓缩后的示意(去掉了大量细节):public class SkuStock { private final SkuId skuId; private final Quantity available; private final List<Preemption> preemptions; public PreemptResult preempt(PreemptCommand command) { if (available.isLessThan(command.getQuantity())) { throw new BusinessException("闪购库存不足"); } Preemption p = Preemption.create(command.getPreemptionNo(), skuId, command.getQuantity()); preemptions.add(p); return PreemptResult.of(StockPreempted.of(p)); } public ReleaseResult release(ReleaseCommand command) { Preemption p = findByNo(command.getPreemptionNo()); p.release(); return ReleaseResult.of(StockReleased.of(p)); } }这种代码最大的好处是领域规则集中在聚合根里,“不允许超卖”这条业务规则你一眼能看到,而不是藏在某个Service第80行的if判断里。3.3 三层验证机制:编译通过、单元测试、契约测试AI生成的代码一定不能直接合入主干。我们的做法是三道闸:第一层是编译和静态检查。生成完代码后,让AI自己检查一遍依赖和语法,然后跑构建;跑不过就带着报错信息“回喂”给AI修改。这一层能过滤掉90%的低级错误。第二层是领域级单元测试。因为我们要求领域层不依赖Spring,所以单元测试很容易写。让AI根据生成的聚合代码,同时生成对应的测试用例,覆盖正常路径和异常路径。异常路径特别重要,比如“库存不足时预占应抛出异常且不产生预占记录”。第三层是契约测试。我们用一条条领域事件作为契约,验证“执行命令后是否产生了预期的事件序列”。这一步能抓住很多AI的“自以为是”:比如它可能在聚合里直接调了数据库,违反了纯领域规则;或者抛异常时把业务规则暴露成了NullPointerException。三层验证跑完,代码才真正进入人工代码评审流程。3.4 增量生成策略:一次只生成一个用例,别让AI一口气写完我们在早期犯过一个严重错误:希望AI一次性生成整个订单模块,约好了所有聚合、所有流程,结果AI生成了几千行代码,结构没错,但细节一塌糊涂:命名不一致、校验规则漏了、事件顺序错乱。改起来比手写还痛苦。后来我们把大任务拆成“用例级”的增量生成。一次只聚焦一个业务场景,比如“用户提交订单并成功预占库存”,生成对应的应用服务、领域服务、领域事件和测试;验证通过后,再生成“支付成功”“超时取消”等场景。这样做还有一个额外的好处:模块与模块之间天然形成“已交付”的边界,代码评审负担小,回归风险也小。AI适合当“车间工人”,按工单一个个生产;谁让AI直接当“总包”交付一栋楼,谁就得自食苦果。4. 52%和75%是怎么算出来的:量化口径与真实占比4.1 代码量基线不是总行数,而是“有效变更行数”很多人看到“代码量减少52%”会本能地问:是不是把老代码里的注释、空行、自动生成代码都剔掉了?我们统计的时候确实做了区分,而且我认为这才是对的口径。我们对比的不是git总行数,而是“业务逻辑的有效变更行数”。具体算法是:先按模块拆分,剔除空行、注释、第三方模板代码、数据库脚本和构建文件,再统计领域层、应用层、基础设施层里需要人工维护的业务代码。下表是重构前后的部分模块对比(数据经过脱敏处理,但比例是真实的):模块重构前有效代码行数重构后有效代码行数下降比例下单流程81003900-52%优惠计算54002300-57%库存预占与回补47002600-45%订单状态流转63003100-51%合计2450011900-51.4%代码量减少的核心来源有三个:一是多个Service里重复的状态流转逻辑被收敛到订单聚合;二是原来散落的优惠判断规则被统一到优惠聚合;三是大量模板式CRUD代码被通用仓储和事件发布机制替代。还有一点容易被忽略:新代码中“领域规则”的密度反而变高了。同样的代码量,含金量完全不一样。这也是我后来坚持向大家强调的——不要只盯着行数,要看代码里“死逻辑”和“活逻辑”的比例。死的CRUD少一半,活的业务规则更集中,这才是重构最大的收获。4.2 成本降低75%的统计口径:不是“人力投入”而是“业务交付成本”“成本降低75%”听起来很夸张,但它不是指程序员总工资少了75%,而是指“单需求平均交付成本”。我们以过去一个季度为观察窗口,统计里包括沟通、设计、开发、联调、测试、上线、返工的全部人天。重构前,一个中等复杂度的闪购需求(比如“新增一种限购规则”)平均要14个工作日;重构后,同样类型需求平均只要3个工作日,降幅接近79%。平均下来按75%计是有依据的。这背后有两层效率来源:第一层来自DDD,边界清楚了,联调变成“填充接口之间的缝隙”,而不是“互相猜对方逻辑”;第二层来自AI,机械编码量大幅减少,开发把时间花在写契约、评审、测试上。4.3 这些数字有多少来自AI,多少来自DDD我特别想给这个量化结论泼一点冷水。不少团队看到“AI+DDD”就觉得是AI的功劳,我们内部复盘后的结论是:五五开。DDD带来的收益是系统性的:边界清晰、命名统一、领域规则收敛,这些和AI没有直接关系,是人脑建模的结果。AI带来的收益是执行侧:同样的建模成果,传统编码要28个工作日,AI辅助后大约10个工作日就能完成,省下的是纯粹的机械劳动时间。所以如果要复刻这个方案,千万别想着“买了AI工具就能自动重构”。AI省下的只是“从设计图到代码”的距离,而真正决定重构成败的,是设计图本身的质量。5. AI生成的“完美代码”为什么不能直接上生产:我们踩过的坑5.1 提示词写得太细,AI产出反而更僵化我们一开始追求“提示词越细越好”,把所有类的命名、所有方法的签名、所有目录结构都写清楚,结果AI输出的代码像“用模板硬套”,出现了大量重复且无意义的中间方法,甚至出现了同一种校验逻辑在三个类里各写一份的情况。代码评审时被同事指着鼻子问:这真的是AI写的?看着比人写的还烂。后来我们调整了策略:提示词里只约束“边界、规则、不变量、依赖方向”,不约束“方法内部怎么实现”。给AI留出表达空间,它反而能生成更干净的代码。这就像你给设计师定“风格、配色、预算”,但别指挥人家“这里加个按钮、那里加个像素”。5.2 领域事件被AI“优化”得越来越像旧的事务脚本我们让AI按“命令->领域方法->领域事件”的模式生成代码后,它经常把领域事件直接转成对数据库的更新操作,比如在应用服务里写“先扣库存、再算优惠、再创建订单”,把事件当成注释而不是一等公民。这是LLM被老代码风格“污染”的结果。我们的解法是,在代码评审中增加一条硬性规定:应用服务只做两件事——把命令转给领域层,然后发布领域事件。谁要是把业务规则写在应用层,谁就重写。规则简单粗暴,但管用。5.3 过度抽象:AI自动创造了三个泛型基类有一次AI为了让代码“更优雅”,自动造了一套抽象层体系:BaseAggregate、BaseService、BaseRepository,三层泛型嵌套,看起来很高大上,实际用起来要打开五六个文件才能理解一个最简单的查询。这里我要说一句可能不讨喜的话:过度抽象比重复代码更可怕。重复代码至少每个地方都能独立改,抽象一旦失控,改一个细节就牵一发动全身。我们对AI生成的代码专门加了一条约束:禁止为了减少重复而引入新的抽象层;如果真的出现明显重复,先合并,但合并前要有人工评审确认。5.4 代码注释里藏着幻觉:生成代码与实际逻辑不一致AI生成的注释特别有迷惑性。它可能写“如果用户支付超时,则取消订单并释放库存”,但实际代码只在部分分支做了释放,注释属于“脑补合理场景”。这个问题比代码错误更难发现,因为注释会骗你,让你跳过真实逻辑。我们后来专门做了一轮“注释与代码一致性检查”,把AI生成的注释提取出来,逐条对代码核对。这轮检查帮我们抓出了十多个潜在线上故障。5.5 回归测试覆盖要人工盯:AI改代码时经常把边界情况写丢AI的“上下文记忆”其实比人还差。它帮你修了一个库存扣减的Bug,可能顺手把“数量为0时校验失败”的分支删掉了;第三天你回过头来才发现不对劲。我们的对策是:所有AI修改过的代码,必须连带更新对应的单测用例,而且测试用例要覆盖到边界值。没有测试用例守护的AI重构,等于在悬崖边开车。这条规则看起来是常识,但在AI生成的节奏下,特别容易被忽略——因为它修改得太快了,快到人类评审根本来不及反应。6. 什么样的团队和系统适合复刻这套打法6.1 团队画像:领域专家、架构师、程序员三者的分工如何调整AI辅助DDD重构对团队的要求不是变低了,而是变了。传统DDD需要领域专家、架构师、程序员三者密切配合;引入AI后,架构师的工作量更重了——他要把建模契约写得足够精确,才能让AI稳定输出;程序员的工作量从“写代码”变成了“验证代码、评审代码、修边界”;领域专家依然要参与事件风暴,但要更早、更频繁地做决策。如果我们团队缺少任何一个角色,这套打法都会出问题。没有领域专家,建模契约就是空壳,AI生成的代码再规范也没用;没有架构师,契约里全是漏洞;没有会写提示词和做评审的程序员,AI生成的就是一堆需要返工的半成品。6.2 系统画像:什么样的老系统适合“AI+DDD”重构这套打法不是万能的。根据我们自己的观察,适合重构的老系统通常有三个特征:业务稳定,核心流程长期不会大改;规则复杂但明确,可以被梳理成清晰的事件轴;历史包袱重,但新旧系统可以逐步替换。反过来,如果业务本身还在剧烈变化期,比如每两周就要新增一个完全没想清楚的活动玩法,那DDD反而会拖慢节奏,因为建模赶不上变化。这种场景下,优先用轻量流程先把业务跑通,再考虑领域建模。6.3 三个必要条件:事件风暴记录、自动化测试基线、统一的代码生成脚手架想复刻,至少要提前准备好三样东西:第一,完整的事件风暴记录。这是AI生成代码时最重要的业务上下文。文档要结构化,按聚合、事件、命令、规则分别整理,而不是把会议纪要丢给AI。第二,自动化测试基线。重构之前的旧系统最好有一套覆盖面足够广的回归测试,否则新代码跑得再快,你也不敢上线。我们重构时先花了一周时间给核心链路补了冒烟测试,这一步帮我们兜住了大量重构引入的回归。第三,统一的代码生成脚手架。AI生成代码时需要一个固定的目标结构,包括目录分层、依赖规则、事件总线、仓储框架。脚手架不统一,AI生成的代码风格就会飘,评审成本骤增。6.4 不建议复刻的场景:规则频繁变动且领域专家严重缺位的系统最后说一个容易被忽略的反例。我们在另一个项目里尝试过同样的方案,那个系统是后台配置管理类系统,业务规则几乎隔几天就变,而且没有专职的领域专家,全靠开发自己猜业务意图。结果AI生成的建模契约很快过时,代码从“领域清晰”退化成“多了一层壳的CRUD”,收益远低于预期。那段经历让我更坚定一个看法:AI+DDD的杠杆,只有在“建模质量足够高”的时候才是正的。建模质量不够,AI只会放大混乱,不会制造秩序。所以,如果你所在团队连一次认真的业务对齐会议都凑不齐人,建议先别急着上AI,先把人和业务对齐了再说。闪购系统重构完到现在已经跑了一个大促周期,最直观的感受是:以前大促前要开好几次紧急会议梳理链路,这次基本是照着事件轴线对一遍就行。AI和DDD的组合给我的启发是,工具再强,也只是把人的判断打磨得更锋利,而判断本身——业务边界在哪、哪些规则不能妥协、哪些职责必须回收——永远是团队自己做的功课。
去年我们接到一个挺棘手的任务:重构一套每天高峰要扛几十万级并发请求、但代码还停留在事务脚本阶段的闪购系统。当时我对DDD能不能在这种老系统上落地,心里是没底的——不是概念难,而是老系统的业务逻辑早散得到处都是,靠人工梳理,工期根本排不开。后来我们换了个思路:把AI当成“建模速记员”和“代码生成工人”,把团队里的架构师和领域专家拉回到建模本身。重构结束后的结果,就是标题里那两个数字:核心链路代码量减少52%,单需求平均交付成本下降超过75%。这篇把整套打法完整拆开,从为什么选DDD、AI在建模环节怎么用,到代码生成和量化口径,最后是我们踩过的坑,尽量讲透。需要先说明的是,为避免不必要的品牌指向,下文所有业务描述都以“某电商平台的闪购业务系统”为背景。这套方法的适用面远不止电商,凡是存在大量业务规则、多人协作、长期演进的中后台系统,思路都可以平移。1. 为什么说老系统改不动,才轮到DDD出手1.1 一个典型闪购模块的事务脚本长什么样几乎所有“改不动”的系统,病灶都很相似。拿闪购最常见的下单场景来说,传统代码往往是一个巨大的下单Service,里面依次塞了几十个步骤:校验活动、校验用户资格、查询库存、扣减库存、计算优惠、校验优惠、创建订单、发送消息……每一步都直接操作数据库,中间夹杂着一堆if else。业务上明明有“闪购活动”“库存”“订单”“优惠券”这些清晰概念,代码里却被压扁成一张“流水线表”。更麻烦的是,订单状态直接散落在多个Service里,支付回调改一下订单状态,超时任务也改一下订单状态,活动减库存和售后加库存也各写各的。久而久之,“订单状态机”成了薛定谔的猫——谁都在摸,谁都不知道全貌。这种代码其实有个专门的名字:贫血模型加事务脚本。它最大的问题不是行数多,而是“领域逻辑”和“技术实现”完全搅在一起。改一个优惠规则,可能要联动下单、支付、对账三个服务;加一个新活动类型,几乎要把整条链路重新走一遍。1.2 没有领域边界的代价:改一个需求要多花多少成本我们重构前做过一次粗略统计:过去一年团队60%的迭代都集中在下单与促销模块,而这两个模块的代码耦合度最高。一个典型的“延长闪购活动时间”需求,放在老系统里要动7个接口、20多处状态判断,联调时谁都不敢保证没漏。这里最耗时的不是写代码,而是开会。业务同学说“活动快照”,开发理解成“把活动配置复制一份”;业务说“超时取消”,开发理解成“把订单状态改成已取消”。术语不一致,代码里的命名跟着漂移——同一个概念在不同服务里有三个名字。这就是典型的缺“统一语言”(Ubiquitous Language)。DDD的核心理念说白了就两件事:第一,先把业务的边界划清楚,每个领域模型只关心自己的事;第二,用统一的业务语言沟通,代码命名跟着业务术语走,而不是跟着数据库表名走。1.3 AI在重构里的真实定位:不是替代架构师,而是放大产能很多文章把“AI重构”写得像魔法:丢一个旧项目进去,AI自动吐出漂亮整洁的新代码。实际完全不是这样。我们尝试过让AI直接“翻译”老代码,结果它把事务脚本里的坏味道原封不动搬到了新代码里——因为老代码本身就是一种不太合理的表达,AI照着学,只能学到错误范式。真正的分工应该是:人负责“知道业务到底想要什么”,AI负责“把已经想清楚的业务翻译成高质量代码”。说得再直白一点,DDD的建模过程特别适合AI参与,因为建模需要大量枚举事件、整理词汇、拆分工序,这些是LLM的强项;而最终的边界划分、聚合设计、业务规则取舍,仍然要人来拍板。我们团队当时有个判断:如果硬靠人工做事件风暴和代码迁移,至少得8周;AI只能帮我们压缩工期,不能帮我们做决策。实际干下来,这个判断是对的。AI把我们从大量机械劳动里解放出来,让我们有更多时间讨论真正的业务问题。2. 事件风暴先行:AI怎样把业务规则变成统一语言2.1 事件风暴不是“开会”,而是一条事件时间轴大多数团队对事件风暴的理解容易跑偏,觉得就是邀请业务专家和技术人员坐在一起贴便签纸。其实事件风暴的核心产出物,是一条按时间顺序排列的事件流。比如闪购下单链路,我们梳理出这样一串核心事件:用户提交下单请求系统校验闪购活动资格系统锁定活动库存系统计算并应用优惠订单状态变为已提交支付结果通知到达订单状态变为已支付用户未支付,系统触发超时取消库存回补每个事件都对应一个明确的业务结果。这么做最大的价值,是把“业务发生了什么”和“系统要做什么”分开了。事件说出来,业务专家听得懂;落到代码里,事件就是领域事件消息,结构也自然清晰。2.2 用AI做流程文本清洗和事件提取我们手头有一大堆老接口文档、历史需求单、会议纪要,全是口语化的描述。比如“用户下单的时候如果这个券已经过期了,那就不能用,还要给提示”。这些话句式混乱、术语不统一,靠人读非常低效。我的做法是把这些原始文本整理后扔给AI,让它先做“事件提取”,提示词大致长这样:下面是一段闪购下单流程的业务描述,请提取出: 1. 所有业务事件(用“xxx已完成/已拒绝”的过去时命名) 2. 触发这些事件的命令或动作 3. 每个事件发生时需要校验的业务规则 4. 涉及的实体名词 要求:统一名词,不要出现“订单状态改为”这类技术表述,改用业务事件描述,例如“订单已提交”。原始描述如下: ……这一步产出的候选事件可能有一百多个,有一定重复和噪音,但没关系。AI负责把覆盖面铺开,人工负责收敛和纠正。实测下来,AI提取出的候选事件覆盖了老系统80%以上的行为,剩下20%藏在代码逻辑里或者只在业务口头流传,必须靠领域专家补充。2.3 提示词里最重要的三个约束:统一语言、事件命名规范、命令与事件的区分用AI做事件提取,提示词里的约束比问题本身更重要。我们迭代了很多轮,最终沉淀出三个关键约束:第一,强制统一语言。业务同学可能一会儿说“过期”、一会儿说“失效”,但代码里只能选一个词。我们在提示词里明确:“以下每个概念只能有一个标准名称,请先给出一份术语对照表”。第二,强制事件采用过去时命名。因为事件代表已经发生的事实,用“订单已取消”而不是“取消订单”。这个约束能让后续领域事件的建模一致得多。第三,强制区分“命令”和“事件”。命令是外部请求,比如“用户提交订单”;事件是系统内部产生的已发生事实,比如“订单已提交”。AI如果不区分,很容易把命令和事件混在一起,导致领域模型语义混乱。这里分享一个我们踩过的小坑:有一次为了省事,没在提示词里约定事件命名,AI生成了“订单超时取消”“订单支付成功”这样的中英混杂命名,看起来没问题,但后面做事件溯源时发现,消息主题名称和事件类名对不上,排查了很久。统一命名规范这件事,越早定越好。2.4 建模产物如何反向校验业务:用“如果”测试逼问边界事件风暴产出事件轴以后,不能直接进编码,还要做一轮边界校验。我习惯用“如果……会怎样”来逼问模型:如果用户下单时活动库存只剩1件,但用户同时下了2件,系统怎么处理?如果用户支付时优惠券已经过期,事件顺序应该是“优惠失效”还是“支付失败”?如果支付回调延迟了2小时,超时取消任务和支付回调谁先到?这些场景AI是没法代替业务判断的,但可以用AI帮忙列候选场景。做法是把已经梳理好的事件轴和聚合边界发给AI,让它扮演“毒舌测试员”,列出所有可能的边界场景清单,再让业务专家逐个确认。这一步看起来不起眼,却直接决定聚合设计是否可靠。因为聚合的本质就是“保证不变量”的边界——比如库存聚合要保证“不能超卖”,优惠聚合要保证“一张券同一个订单只能用一次”。边界没划清楚,后面生成代码越快,返工越多。3. 让AI生成领域层代码:提示词、脚手架与三层验证3.1 从事件轴到聚合设计:把“锁库存”变成领域服务事件轴定完之后,下一步是把事件归类到聚合。例如“库存锁定”“库存回补”这些事件,加上“查询剩余库存”“预占库存”这些命令,显然应该聚到同一个库存聚合里;优惠相关的事件聚到优惠券聚合;订单状态变迁聚到订单聚合。聚合的粒度不能太小,否则又退回事务脚本;也不能太大,否则聚合内并发冲突严重。我们的经验是,一次购物车结算涉及到的库存预占、优惠计算、订单生成,分别拆成三个聚合,通过领域事件解耦,而不是把三个聚合塞进一个“大结算根”。这一步设计好了,AI生成代码的“输入”就齐了。我们把聚合清单、事件清单、命令清单、业务规则清单打包成一份结构化的“建模契约”,作为后续所有生成提示词的锚点。3.2 提示词工程模板:让AI输出DDD分层代码以下是我们实际使用过、效果还不错的生成提示词模板,核心思路是“先给上下文,再给任务,最后给约束”。你可以直接复制改造:你是一位资深DDD技术专家。下面是一个“库存聚合”的建模契约: 聚合名称:库存 核心实体:sku库存 不变量:闪购库存不允许超卖 领域命令: - 预占库存(请求携带skuId、数量、活动Id) - 释放预占(请求携带预占单号) 领域事件: - 库存已预占(事件携带预占单号、skuId、数量) - 库存已释放(事件携带预占单号、skuId、数量) 仓储接口: - 按skuId查找可用库存 - 保存预占记录 请按端口-适配器架构生成Java代码,要求: 1. 领域层不依赖Spring、不依赖数据库; 2. 聚合内部的业务规则写在领域服务中,而不是写在应用服务中; 3. 外部输入通过命令对象传入,所有校验失败抛出明确业务异常; 4. 返回结果使用领域事件+返回值,不要直接在聚合里发消息。根据这个模板生成出的聚合根代码,结构基本符合预期。下面是一段浓缩后的示意(去掉了大量细节):public class SkuStock { private final SkuId skuId; private final Quantity available; private final List<Preemption> preemptions; public PreemptResult preempt(PreemptCommand command) { if (available.isLessThan(command.getQuantity())) { throw new BusinessException("闪购库存不足"); } Preemption p = Preemption.create(command.getPreemptionNo(), skuId, command.getQuantity()); preemptions.add(p); return PreemptResult.of(StockPreempted.of(p)); } public ReleaseResult release(ReleaseCommand command) { Preemption p = findByNo(command.getPreemptionNo()); p.release(); return ReleaseResult.of(StockReleased.of(p)); } }这种代码最大的好处是领域规则集中在聚合根里,“不允许超卖”这条业务规则你一眼能看到,而不是藏在某个Service第80行的if判断里。3.3 三层验证机制:编译通过、单元测试、契约测试AI生成的代码一定不能直接合入主干。我们的做法是三道闸:第一层是编译和静态检查。生成完代码后,让AI自己检查一遍依赖和语法,然后跑构建;跑不过就带着报错信息“回喂”给AI修改。这一层能过滤掉90%的低级错误。第二层是领域级单元测试。因为我们要求领域层不依赖Spring,所以单元测试很容易写。让AI根据生成的聚合代码,同时生成对应的测试用例,覆盖正常路径和异常路径。异常路径特别重要,比如“库存不足时预占应抛出异常且不产生预占记录”。第三层是契约测试。我们用一条条领域事件作为契约,验证“执行命令后是否产生了预期的事件序列”。这一步能抓住很多AI的“自以为是”:比如它可能在聚合里直接调了数据库,违反了纯领域规则;或者抛异常时把业务规则暴露成了NullPointerException。三层验证跑完,代码才真正进入人工代码评审流程。3.4 增量生成策略:一次只生成一个用例,别让AI一口气写完我们在早期犯过一个严重错误:希望AI一次性生成整个订单模块,约好了所有聚合、所有流程,结果AI生成了几千行代码,结构没错,但细节一塌糊涂:命名不一致、校验规则漏了、事件顺序错乱。改起来比手写还痛苦。后来我们把大任务拆成“用例级”的增量生成。一次只聚焦一个业务场景,比如“用户提交订单并成功预占库存”,生成对应的应用服务、领域服务、领域事件和测试;验证通过后,再生成“支付成功”“超时取消”等场景。这样做还有一个额外的好处:模块与模块之间天然形成“已交付”的边界,代码评审负担小,回归风险也小。AI适合当“车间工人”,按工单一个个生产;谁让AI直接当“总包”交付一栋楼,谁就得自食苦果。4. 52%和75%是怎么算出来的:量化口径与真实占比4.1 代码量基线不是总行数,而是“有效变更行数”很多人看到“代码量减少52%”会本能地问:是不是把老代码里的注释、空行、自动生成代码都剔掉了?我们统计的时候确实做了区分,而且我认为这才是对的口径。我们对比的不是git总行数,而是“业务逻辑的有效变更行数”。具体算法是:先按模块拆分,剔除空行、注释、第三方模板代码、数据库脚本和构建文件,再统计领域层、应用层、基础设施层里需要人工维护的业务代码。下表是重构前后的部分模块对比(数据经过脱敏处理,但比例是真实的):模块重构前有效代码行数重构后有效代码行数下降比例下单流程81003900-52%优惠计算54002300-57%库存预占与回补47002600-45%订单状态流转63003100-51%合计2450011900-51.4%代码量减少的核心来源有三个:一是多个Service里重复的状态流转逻辑被收敛到订单聚合;二是原来散落的优惠判断规则被统一到优惠聚合;三是大量模板式CRUD代码被通用仓储和事件发布机制替代。还有一点容易被忽略:新代码中“领域规则”的密度反而变高了。同样的代码量,含金量完全不一样。这也是我后来坚持向大家强调的——不要只盯着行数,要看代码里“死逻辑”和“活逻辑”的比例。死的CRUD少一半,活的业务规则更集中,这才是重构最大的收获。4.2 成本降低75%的统计口径:不是“人力投入”而是“业务交付成本”“成本降低75%”听起来很夸张,但它不是指程序员总工资少了75%,而是指“单需求平均交付成本”。我们以过去一个季度为观察窗口,统计里包括沟通、设计、开发、联调、测试、上线、返工的全部人天。重构前,一个中等复杂度的闪购需求(比如“新增一种限购规则”)平均要14个工作日;重构后,同样类型需求平均只要3个工作日,降幅接近79%。平均下来按75%计是有依据的。这背后有两层效率来源:第一层来自DDD,边界清楚了,联调变成“填充接口之间的缝隙”,而不是“互相猜对方逻辑”;第二层来自AI,机械编码量大幅减少,开发把时间花在写契约、评审、测试上。4.3 这些数字有多少来自AI,多少来自DDD我特别想给这个量化结论泼一点冷水。不少团队看到“AI+DDD”就觉得是AI的功劳,我们内部复盘后的结论是:五五开。DDD带来的收益是系统性的:边界清晰、命名统一、领域规则收敛,这些和AI没有直接关系,是人脑建模的结果。AI带来的收益是执行侧:同样的建模成果,传统编码要28个工作日,AI辅助后大约10个工作日就能完成,省下的是纯粹的机械劳动时间。所以如果要复刻这个方案,千万别想着“买了AI工具就能自动重构”。AI省下的只是“从设计图到代码”的距离,而真正决定重构成败的,是设计图本身的质量。5. AI生成的“完美代码”为什么不能直接上生产:我们踩过的坑5.1 提示词写得太细,AI产出反而更僵化我们一开始追求“提示词越细越好”,把所有类的命名、所有方法的签名、所有目录结构都写清楚,结果AI输出的代码像“用模板硬套”,出现了大量重复且无意义的中间方法,甚至出现了同一种校验逻辑在三个类里各写一份的情况。代码评审时被同事指着鼻子问:这真的是AI写的?看着比人写的还烂。后来我们调整了策略:提示词里只约束“边界、规则、不变量、依赖方向”,不约束“方法内部怎么实现”。给AI留出表达空间,它反而能生成更干净的代码。这就像你给设计师定“风格、配色、预算”,但别指挥人家“这里加个按钮、那里加个像素”。5.2 领域事件被AI“优化”得越来越像旧的事务脚本我们让AI按“命令->领域方法->领域事件”的模式生成代码后,它经常把领域事件直接转成对数据库的更新操作,比如在应用服务里写“先扣库存、再算优惠、再创建订单”,把事件当成注释而不是一等公民。这是LLM被老代码风格“污染”的结果。我们的解法是,在代码评审中增加一条硬性规定:应用服务只做两件事——把命令转给领域层,然后发布领域事件。谁要是把业务规则写在应用层,谁就重写。规则简单粗暴,但管用。5.3 过度抽象:AI自动创造了三个泛型基类有一次AI为了让代码“更优雅”,自动造了一套抽象层体系:BaseAggregate、BaseService、BaseRepository,三层泛型嵌套,看起来很高大上,实际用起来要打开五六个文件才能理解一个最简单的查询。这里我要说一句可能不讨喜的话:过度抽象比重复代码更可怕。重复代码至少每个地方都能独立改,抽象一旦失控,改一个细节就牵一发动全身。我们对AI生成的代码专门加了一条约束:禁止为了减少重复而引入新的抽象层;如果真的出现明显重复,先合并,但合并前要有人工评审确认。5.4 代码注释里藏着幻觉:生成代码与实际逻辑不一致AI生成的注释特别有迷惑性。它可能写“如果用户支付超时,则取消订单并释放库存”,但实际代码只在部分分支做了释放,注释属于“脑补合理场景”。这个问题比代码错误更难发现,因为注释会骗你,让你跳过真实逻辑。我们后来专门做了一轮“注释与代码一致性检查”,把AI生成的注释提取出来,逐条对代码核对。这轮检查帮我们抓出了十多个潜在线上故障。5.5 回归测试覆盖要人工盯:AI改代码时经常把边界情况写丢AI的“上下文记忆”其实比人还差。它帮你修了一个库存扣减的Bug,可能顺手把“数量为0时校验失败”的分支删掉了;第三天你回过头来才发现不对劲。我们的对策是:所有AI修改过的代码,必须连带更新对应的单测用例,而且测试用例要覆盖到边界值。没有测试用例守护的AI重构,等于在悬崖边开车。这条规则看起来是常识,但在AI生成的节奏下,特别容易被忽略——因为它修改得太快了,快到人类评审根本来不及反应。6. 什么样的团队和系统适合复刻这套打法6.1 团队画像:领域专家、架构师、程序员三者的分工如何调整AI辅助DDD重构对团队的要求不是变低了,而是变了。传统DDD需要领域专家、架构师、程序员三者密切配合;引入AI后,架构师的工作量更重了——他要把建模契约写得足够精确,才能让AI稳定输出;程序员的工作量从“写代码”变成了“验证代码、评审代码、修边界”;领域专家依然要参与事件风暴,但要更早、更频繁地做决策。如果我们团队缺少任何一个角色,这套打法都会出问题。没有领域专家,建模契约就是空壳,AI生成的代码再规范也没用;没有架构师,契约里全是漏洞;没有会写提示词和做评审的程序员,AI生成的就是一堆需要返工的半成品。6.2 系统画像:什么样的老系统适合“AI+DDD”重构这套打法不是万能的。根据我们自己的观察,适合重构的老系统通常有三个特征:业务稳定,核心流程长期不会大改;规则复杂但明确,可以被梳理成清晰的事件轴;历史包袱重,但新旧系统可以逐步替换。反过来,如果业务本身还在剧烈变化期,比如每两周就要新增一个完全没想清楚的活动玩法,那DDD反而会拖慢节奏,因为建模赶不上变化。这种场景下,优先用轻量流程先把业务跑通,再考虑领域建模。6.3 三个必要条件:事件风暴记录、自动化测试基线、统一的代码生成脚手架想复刻,至少要提前准备好三样东西:第一,完整的事件风暴记录。这是AI生成代码时最重要的业务上下文。文档要结构化,按聚合、事件、命令、规则分别整理,而不是把会议纪要丢给AI。第二,自动化测试基线。重构之前的旧系统最好有一套覆盖面足够广的回归测试,否则新代码跑得再快,你也不敢上线。我们重构时先花了一周时间给核心链路补了冒烟测试,这一步帮我们兜住了大量重构引入的回归。第三,统一的代码生成脚手架。AI生成代码时需要一个固定的目标结构,包括目录分层、依赖规则、事件总线、仓储框架。脚手架不统一,AI生成的代码风格就会飘,评审成本骤增。6.4 不建议复刻的场景:规则频繁变动且领域专家严重缺位的系统最后说一个容易被忽略的反例。我们在另一个项目里尝试过同样的方案,那个系统是后台配置管理类系统,业务规则几乎隔几天就变,而且没有专职的领域专家,全靠开发自己猜业务意图。结果AI生成的建模契约很快过时,代码从“领域清晰”退化成“多了一层壳的CRUD”,收益远低于预期。那段经历让我更坚定一个看法:AI+DDD的杠杆,只有在“建模质量足够高”的时候才是正的。建模质量不够,AI只会放大混乱,不会制造秩序。所以,如果你所在团队连一次认真的业务对齐会议都凑不齐人,建议先别急着上AI,先把人和业务对齐了再说。闪购系统重构完到现在已经跑了一个大促周期,最直观的感受是:以前大促前要开好几次紧急会议梳理链路,这次基本是照着事件轴线对一遍就行。AI和DDD的组合给我的启发是,工具再强,也只是把人的判断打磨得更锋利,而判断本身——业务边界在哪、哪些规则不能妥协、哪些职责必须回收——永远是团队自己做的功课。