1. 先说清楚DDD到底在解决什么问题
有些团队买了不少DDD相关的书,也参加过培训,但落地的时候还是写成了CRUD——Service里面全是业务逻辑,实体变成纯数据容器,领域事件沦为摆设。这不是个别现象,我见过太多团队把DDD当成一套代码规范来执行,结果只学到了"分层""聚合""仓储"这些表面的形态,完全没有理解它真正要解决的问题是什么。
DDD,领域驱动设计,核心目标只有一个:在业务复杂度高的系统里,让代码结构能真实反映业务结构,让业务语言的变动能快速传导到代码实现层面。
它不是用来解决技术问题的。如果你的系统只是简单的增删改查,或者技术瓶颈在性能、并发、分布式一致性这些层面,那DDD大概率帮不上忙,甚至会让代码变得更啰嗦。我经常跟人讲一个判断标准:当业务规则复杂到"代码里表达不清楚"的地步,当业务人员和开发人员对同一个名词的理解开始分叉,当每一次需求变更都牵动全身的修改,这时候才该认真考虑引入DDD。
这里要先区分两个层面的东西——战略设计和战术设计。战略设计解决的是"系统边界怎么划、模块之间怎么通信"的问题,核心工具是限界上下文(Bounded Context)和上下文映射(Context Mapping)。战术设计解决的是"某个上下文内部怎么建模"的问题,核心工具是实体、值对象、聚合、领域服务、领域事件、仓储这些模式。
很多人上手DDD就是研究战术模式,天天琢磨聚合怎么建、仓储接口怎么定义,却不关心限界上下文划分得对不对。这等于在错误的地基上盖房子,你代码写得再规范,整个系统的边界是乱的,照样处处碰壁。
还有一个误区是把DDD和微服务强行绑定。DDD里的限界上下文确实可以对应微服务的拆分边界,但DDD绝不等于微服务,单体架构里照样可以用DDD来做模块划分。我甚至建议初学者先在单体应用里实践DDD,把建模思想和代码落地这套跑通,再去考虑服务拆分的事,否则建模和分布式问题混在一起,很难定位到底哪里出了问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从业务事件出发:事件风暴的具体做法
那DDD落地第一步该做什么?我的建议是:别急着写代码,先做事件风暴(Event Storming)。
事件风暴是一种工作坊式的建模方法,把业务人员、产品经理、开发人员、测试人员聚在一起,用不同颜色的便利贴在白板上还原整个业务流程。它的核心思路是从业务事件出发反向推导领域模型,不是从数据表出发,也不是从页面原型出发。
具体操作是这样的:
- 先用橙色便利贴把业务领域里发生过的、可能会发生的事件全部列出来。事件一定用过去式描述,比如"订单已创建""库存已扣减""支付已超时"。这一步先求全,不用管顺序和逻辑,把能想到的事件都贴出来。
- 然后所有人一起把这些事件按时间顺序排列,理出一条完整的时间线。排列过程中会发现某些事件的前提不成立、某些环节业务上有分歧,这些分歧点就是建模时要重点关注的业务规则。
- 接着补上触发每个事件的"命令"(通常是用户操作或外部系统的调用),通常在蓝色便利贴上。命令和事件之间会有很自然的因果关系,比如"提交订单"这个命令触发"订单已创建"这个事件。
- 命令必然来自某个"角色",通常用黄色便利贴标注。比如"买家""客服""仓库管理员"。这个角色不一定是系统用户,也可能是外部系统、定时器、甚至是另一个限界上下文。
- 每个命令执行时需要读取或修改的数据,聚在一起就可以识别出"聚合"的雏形。这些和数据相关的概念,通常用白色或绿色便利贴表示。
你别小看这个过程。很多团队做DDD做不下去,就是因为跳过事件风暴直接画类图。事件风暴最大的价值不光是产出领域模型,而是它让业务人员和开发人员用同一种语言把整个业务流程完整过了一遍。过程中会发现很多之前没人注意到的业务规则,比如"订单超过30分钟未支付必须自动关闭"这种规则,它就是一个"支付已超时"事件加上一个定时任务触发"关闭订单"命令的组合。
事件风暴做完之后,白板上会有几十张便利贴,这时候就需要做聚合识别了。聚合是DDD战术设计里最核心也最难把握的概念。简单说,聚合是一组有业务完整性的对象集合,外部只能通过聚合根访问内部对象。再直白一点:每次业务操作需要保证一致性的数据范围,就是一个聚合。
举个例子,电商里订单和订单项就是同一个聚合,因为"订单总金额"这个不变量要求订单和订单项必须同时修改、同时保存。但如果把订单和支付记录放同一个聚合就不太合适,因为支付状态的变化可以独立于订单其他信息的修改,它们各自有各自的完整性边界。这个判断过程需要经验,更重要的是需要业务人员的参与——数据到底该怎么变化,业务规则说了才算。
事件风暴的产出除了领域模型,还有一份非常重要的"副产品"——业务术语表(Ubiquitous Language,通用语言)。风暴过程中出现的每一个业务概念,都要明确它的名称、含义、边界,全团队统一使用。这份术语表后续要一直维护,代码命名、接口字段、需求文档里出现的术语都必须以它为准。很多团队模型建模没问题,最终死在术语混乱上——代码里叫"Order",业务系统里叫"销售单",数据库表叫"t_sales_order",每次沟通都要做一次名词翻译,这是最消耗团队认知成本的事情。
3. 识别限界上下文:系统边界划对了,架构就成功一半
事件风暴做完,你会得到一堆聚合和业务事件。接下来要做的是限界上下文划分——把这些聚合和事件归属到不同的业务边界里。
限界上下文(Bounded Context)是DDD里最重要的战略设计概念,没有之一。一个限界上下文就是一个完整的业务能力边界,它内部有一套自洽的领域模型和业务规则。不同上下文里甚至可以有同名的概念,但它们的含义和模型可以完全不同。
还是拿电商举例。"商品"这个名词,在商品目录上下文里,它包含SPU、SKU、属性、图片、规格这些信息,主要服务于前台展示和检索。但在订单上下文里,"商品"只需要商品ID、名称、单价这几个快照字段,目的是保证下单时价格不被后续修改影响。两个上下文里的"商品"根本不是同一个对象,如果强行复用一个模型,就会导致一个上下文里改了字段,另一个上下文跟着遭殃。
区分限界上下文的方法论主要有两种:按业务能力划分,或者按业务域(子域)划分。按业务能力分比较直观,比如订单、库存、支付、会员、营销,每个核心业务能力就是一个上下文。按子域分需要先做业务域分析,核心域、支撑域、通用域,然后每个子域可以对应一个或多个上下文。
我自己的经验是,事件风暴做完之后,一眼看过去那些聚合之间关系很紧密、业务事件因果链很强的一组,放在同一个上下文;聚合之间只是通过事件通知进行协作的,就放在不同上下文。
