本篇要搞懂的 4 件事:DDD 从需求到代码到底分哪几步、战略设计(事件风暴)怎么从场景一路收敛出领域模型和微服务边界、战术设计怎么把领域模型变成有分层的服务和实体、领域对象最终怎么一一映射到代码里的包/类/方法。
⚠️ 这一讲是知识点串讲——它不教新概念,而是拿"在线请假考勤系统"这一个完整例子,把前面 1-17 讲学过的零件(事件风暴、聚合、聚合根、实体、值对象、限界上下文、分层架构、仓储、依赖倒置……)全部串成一条流水线走一遍。前面哪一讲没消化的,这一讲是最好的对账时机。
承接前文:前 17 讲我们把 DDD 的零件一个个拆开讲了——01-06 讲战略设计的概念(领域、子域、限界上下文、实体值对象、聚合),07-08 讲分层架构和几种架构模型,后面又讲了服务、仓储、依赖倒置这些战术零件。但零件归零件,真上手时该按什么顺序拼?这一讲就给你一条完整的施工路线:战略设计(建模 + 划微服务)→ 战术设计(设计微服务内部 + 映射代码)→ 后续详细设计与开发。
需求(在线请假考勤)
↓ 战略设计(事件风暴)
产品愿景 → 场景分析 → 领域建模(实体→聚合→限界上下文)→ 拆微服务
↓ 战术设计(以请假微服务为例)
识别服务(应用层/领域层)→ 设计聚合内对象 → 列对象清单 → 落成代码结构
↓ 后续
详细设计 → 编码 + 单元测试
🪞 辅助理解(盖楼比喻):战略设计像做城市规划——先定这片地建几栋楼、每栋楼之间怎么分界(划微服务);战术设计像单栋楼的施工图——这栋楼每层放什么、房间怎么隔、水电管线怎么走(设计微服务内部);最后才是装修施工(写代码)。这一讲就是带你把一块空地盖成一栋楼的全过程。
整个例子的需求一句话:请假人提交请假单→按规则逐级审批→通过后把数据送去考勤核销→输出考勤统计。
第一步(战略设计):产品愿景——先统一"我们到底要做个啥"
战略设计的总目标:通过事件风暴,找出领域对象和聚合根,聚成聚合,划出限界上下文,最终建立领域模型。它分四个过程:产品愿景 → 场景分析 → 领域建模 → 微服务拆分。
第一个过程是产品愿景:在产品动工前,让所有人(领域专家、产品、架构师、开发、测试……)对"目标用户、核心价值、和竞品的差异"达成一致,免得做着做着方向跑偏。做法很朴素——每人把意见写贴纸上贴白板,主持人带着大家收敛。
看图抓什么:左边粉色一列是固定句式骨架(为了…、他们的…、这个…、是一个…、它可以…、而不像…、我们的产品…),右边黄色便利贴是团队讨论填进去的内容。把整张图顺着读下来,就拼成一句完整的产品定位话术:
“为了满足内外部人员在线请假、自动考勤统计、外部人员管理的需求,我们建设这个在线请假考勤系统,它内外网皆可使用、能无差异管理内外部人员——而不像 HR 系统只管内部人员、只能内网用。”
💡 这一步对初创系统统一目标、建立通用语言很有价值;但如果你的需求本来就很清晰,这步可以跳过。它是"对齐共识"的工具,不是必须交付物。
第二步(战略设计):场景分析——从用户视角把业务流"演"一遍
产品愿景定了方向,接下来要把业务铺开。场景分析是站在用户视角,把典型场景里"从前端点一下、到后端发生了什么"的所有命令、业务流、领域事件、外部依赖全梳出来,给后面建模喂料。
原文以"请假/审批"和"人员"两个场景举例。请假场景的旅程是:请假人登录(从权限微服务取信息)→ 创建/修改请假单 → 提交审批(取审批规则、从人员组织关系找审批人、分配审批人);审批人则是登录 → 取名下请假单 → 填意见 → 逐级审批 → 最后通过,产生"请假审批已通过"领域事件,再触发两件后续:发通知(邮件系统) 和 把数据送考勤核销。
看图抓什么:注意这张图的"三层结构"——蓝色一行是命令(系统对外提供的操作能力,如"创建请假单"“提交审批”),中间一行是业务流(用户的操作步骤),橙色一行是领域事件(操作后产生的既成事实,如"请假单已创建"“审批已通过”“邮件系统发送通知”)。蓝色块(命令)就是后面识别服务的起点,橙色块(事件)就是后面要异步处理的东西——这两类块先在脑子里标记一下。
下面是另一个场景"人员组织关系"的分析结果(请假要用到它来找审批人):
看图抓什么:和上一张同构——蓝色命令(创建/查询/修改人员、创建/修改组织关系),橙色事件(人员已创建、人员已修改)。左上那张黄色"内部人员从 HR 获取"提示了一个外部依赖:内部人员数据并非本系统产生,要从 HR 系统取。
🪞 辅助理解(剧本杀):场景分析就像写一出戏的剧本——谁(角色)在什么时候做了什么动作(命令)、剧情怎么往下走(业务流)、做完之后留下了什么后果(事件)。把所有戏份都演一遍,你才知道这部戏一共需要哪些"道具"和"角色"——这正是下一步建模要找的实体。
第三步(战略设计):领域建模——三步把模型"收敛"出来
领域建模是个收敛的过程(从一堆零散的命令/事件,慢慢聚成结构清晰的模型),分三小步:① 找实体和值对象 → ② 找聚合根、聚成聚合 → ③ 划限界上下文。它向上指导微服务边界、向下指导实体对象设计,是承上启下的关键。
3.1 找出实体和值对象
做法:盯着场景分析里那些命令和事件,反推是谁发起或产生了它们,把相关命令/事件聚到对应的领域对象上。这一轮收敛出来的实体和值对象有:请假单、审批意见、审批规则、人员、组织关系、刷卡明细、考勤明细、考勤统计。
看图抓什么:颜色是有讲究的——绿色块是领域对象(实体/值对象)本身(请假单、审批意见、审批规则、人员、组织关系、刷卡明细、考勤明细、考勤统计),蓝色块是围绕它的命令,橙色块是它产生的事件。比如最左一堆蓝色命令(提交审批、创建/修改请假单……)全都围着绿色的"请假单"转——这就说明这些命令归属于"请假单"这个对象。一个领域对象 + 围着它的命令和事件 = 一个聚集,这是聚合的雏形。
3.2 找聚合根、定义聚合
先从实体里挑出聚合根(聚合的管理者)——这里挑出"请假单"和"人员"两个。再把和它们紧密依赖的对象拉进来:审批意见、审批规则贴着请假单;组织关系贴着人员。
这里有个很典型的坑值得记住:刷卡明细、考勤明细、考勤统计这三个实体互相独立、找不出聚合根(不是"富领域模型"),但它们一起完成考勤业务、内聚性很高。处理办法:把它们一起塞进一个"考勤聚合",没有聚合根就用传统方式管理实体即可。最终聚成三个聚合:请假聚合、人员组织关系聚合、考勤聚合。
看图抓什么:每个橙色椭圆是一个聚合,圈里的小白圆是聚合内的实体/值对象,圆之间那个小菱形(◇)就是聚合根的标志——请假聚合里"请假单"挂着审批意见和审批规则,人员组织关系聚合里"人员"挂着组织关系;而右下角"考勤"聚合里刷卡记录/考勤明细/考勤统计三个圆互相没有菱形连线,正是"没有聚合根"的视觉信号。
💡 不是所有聚合都必须有聚合根。考勤这种"几个实体平级协作、谁也管不了谁"的弱模型,承认它没根、用传统方式管,比硬塞一个假根更健康。这是建模时的常见判断。
3.3 定义限界上下文
最后按业务语义边界划上下文:人员组织关系聚合和请假聚合共同完成请假业务,归到请假限界上下文;考勤聚合自成一体,归到考勤统计限界上下文。于是建立请假和考勤两个领域模型。
🪞 辅助理解(套娃):建模的三步是一层套一层的收纳——实体/值对象是最小的零件,聚合是把关系紧密的零件装进一个盒子(聚合根是盒子上的把手),限界上下文是把几个盒子放进同一个柜子(因为它们服务于同一块业务)。收纳完,柜子的边界天然就成了微服务的候选边界。
第四步(战略设计):拆分微服务
理论上一个限界上下文就能做成一个微服务,但落地还要综合考虑非业务因素:职责单一、敏态/稳态分离、非功能需求(弹性伸缩、发布频率、安全)、软件包大小、团队沟通效率、技术异构等。
本项目主要按职责单一来拆,于是直接照限界上下文拆成两个微服务:
| 微服务 | 包含的聚合 | 对应限界上下文 |
|---|---|---|
| 请假微服务 | 人员组织关系聚合 + 请假聚合 | 请假 |
| 考勤微服务 | 考勤聚合 | 考勤统计 |
到这里战略设计结束:领域模型建好了、微服务边界划好了。下面切到战术设计,以请假微服务为例往里钻。
💡 注意"请假微服务里塞了两个聚合(请假 + 人员组织关系)“——这埋了个伏笔:以后人员功能要独立,只要把人员聚合的代码包拎出来单独部署,就能快速升级成"人员微服务”。合理的聚合划分,让微服务拆分/合并成本变低,这是后面代码结构那一节会兑现的承诺。
第五步(战术设计):识别和设计服务
战术设计 = 拿领域模型做微服务的内部设计:梳理领域对象、确定它们在分层架构里的位置、建立领域模型与代码模型的映射。它分两阶段:分析领域对象 → 设计代码结构。先看第一阶段里最核心的"识别服务"。
起点是"命令":事件风暴里的命令是微服务对外提供的能力,往往对应应用服务或领域服务。识别服务的步骤是自上而下的:
- 按命令设计应用服务(定它的功能、要组合编排哪些服务——可以是本服务的领域服务,也可以是别的微服务的应用服务);
- 按应用服务的需要设计领域服务(注意:一个应用服务可能要组合多个聚合的领域服务);
- 按领域服务定它内部用到的实体及方法;
- 设计实体的基本属性和方法;
- 别忘了领域事件的异步化处理。
原文以"提交审批"这个动作演示。它的业务流程是:查审批规则取下一审批角色 → 按角色从人员组织关系查到下一审批人 → 给请假单分配审批人并保存审批规则。拆到各层就是:
| 层 | 服务/方法 | 归属聚合 |
|---|---|---|
| 应用层 | 提交审批应用服务(编排下面三个领域服务) | — |
| 领域层 | 查询审批规则、修改请假流程信息 | 请假聚合 |
| 领域层 | 根据审批规则查询审批人 | 人员组织关系聚合 |
| 实体方法 | 请假单.修改请假流程信息、审批规则.查询审批规则、人员.根据审批规则查询审批人 | 各自实体 |
看图抓什么:这张图就是 07/08 讲的分层架构在真实业务里的样子,从上往下四段——
- 用户接口层(绿):Facade 接口收请求、做 DTO→DO 转换,再定向到应用服务;
- 应用层(蓝):"提交审批应用服务"在这里编排第一步查规则、第二步查审批人、第三步改流程信息(注意它只编排、不写业务规则);
- 领域层(橙):分成左右两个聚合(请假聚合、人员组织关系聚合),领域服务再往下"封装"对应的实体方法,实体(红椭圆=聚合根:请假单、人员;绿椭圆=值对象:审批规则)才是真正干活的;
- 最底下两个仓储服务统一连到数据库。
把箭头方向连起来看:请求由上往下穿透各层,业务逻辑落在领域层的实体上,应用层只是个"指挥"——这正是 DDD 分层的精髓在一个具体动作上的兑现。
🪞 辅助理解(餐厅):应用服务像服务员——他不下厨,只负责把"提交审批"这单拆成几道工序,分别喊给后厨;领域服务/实体像厨师——真正炒菜(执行业务规则)的是他们;仓储像仓库管理员——只管食材进出(存取数据)。服务员手里不能藏私房菜谱(业务规则不能写进应用层),否则厨房(领域层)就被架空了。
第六步(战术设计):设计聚合内的对象
服务识别完,往下钻到聚合内部,把每个聚合里到底有哪些实体、值对象定清楚。
请假聚合:聚合根是请假单。多级审批会产生多条审批意见 → 设计成实体;审批通过会产生领域事件 → 有请假事件实体。值对象一堆:请假人、下一审批人(都来自人员聚合的人员实体)、人员类型/请假类型/审批状态(枚举)、审批规则。
看图抓什么:中心是聚合根"请假单",每根连线上的标签是关键——只有"审批意见"标的是实体,其余(请假人、下一审批人、人员类型、请假类型、审批状态、审批规则)全标值对象。这告诉你一个判断习惯:会独立变化、需要单独追踪生命周期的才做实体(审批意见会一条条新增),描述性的、可整体替换的就做值对象。
人员组织关系聚合:聚合根是人员,组织关系是实体(含组织关系类型、上级审批领导),其中组织关系类型(项目经理/处长/总经理…)和上级审批领导是值对象。
看图抓什么:人员(聚合根)和组织关系之间标"实体",组织关系再挂出"上级审批领导""组织关系类型"两个值对象。注意"上级审批领导"本质也是个人员,但在这个聚合里它只是被引用的描述信息,所以降级当值对象——同一个东西在不同聚合里可以是实体也可以是值对象,看它在当前上下文里要不要被独立管理。
6.1 微服务内的对象清单
把所有领域对象的属性定完后,给每个对象指定它在代码里的包名、类名、方法名,建立"领域对象 ↔ 代码对象"的一一映射。有了这张表,谁拿到需求都能快速定位到对应代码位置。
看图抓什么:这是整讲信息密度最高的一张表,重点看它的列结构——「层 | 聚合 | 领域对象名 | 领域类型(聚合根/实体/值对象/方法/领域服务)| 依赖对象 | 包名 | 类名 | 方法名」。比如"请假单 → 聚合根 → leave.domain.leave.entity → Leave → createLeaveInfo/getLeaveInfo…"。这张表就是"建模"和"写代码"之间的那座桥:左半边是业务语言,右半边是 Java 包类名,一一对上号,开发就不会无处下手。
💡 这张映射表是 DDD"代码模型"落地的核心交付物。它把"领域模型"翻译成了"工程结构",让业务可追溯到代码、代码也能反查业务——后面要改需求时,照着表就能精准找到该动哪个类哪个方法。
第七步(战术设计):落成微服务代码结构
最后照着对象清单,把代码目录搭出来。
应用层:放应用服务、DTO、事件发布相关代码。LeaveApplicationService 实现聚合相关应用服务,LoginApplicationService 封装调外部"认证权限"微服务的应用服务。
看图抓什么:application 下两个包——event/publish/ApprovalEventPublish.java(领域事件的发布)和 service/(两个应用服务类)。结构很薄,应用层就该薄,它只做编排和事件发布。
💡 提醒:应用服务逻辑复杂时,一个应用服务就单独建一个类,别全堆进一个大类里,否则后期难维护。
领域层:放一个或多个聚合的实体类、事件实体类、领域服务、工厂、仓储。一个聚合对应一个代码目录,聚合之间代码上完全隔离,靠应用层协调。
看图抓什么:domain 下有 leave 和 person 两个聚合包,两者结构完全对称——各自都有 entity(实体类,如 Leave/Applicant/Approvor/Status… 和 Person/Leader/Relationship…)、event、repository(又分 facade 仓储接口 / mapper / persistence 仓储实现)、service(领域服务)。这个对称隔离正是前面伏笔的兑现:人员功能要独立时,把 person 整个包拎出来稍加改造、独立部署,就是一个新的"人员微服务"——聚合的物理隔离让拆分成本极低。
🪞 辅助理解(积木盒):聚合 = 一盒独立的乐高,每盒自带说明书(entity/repository/service 一套齐全),盒与盒之间不直接拼(不互相调用),要联动得通过外面的"应用层"指挥。哪天想把某盒搬走单独玩(拆成新微服务),整盒端走就行,不会扯出一堆线头。
到这里,请假微服务的总体架构和代码骨架就搭完了。
后续:详细设计 + 编码测试
骨架之后还有两步收尾(这一讲只点到为止):
- 详细设计:实体属性、数据库表/字段、实体与表的映射、服务参数规约及功能实现。
- 编码 + 测试:开发照设计文档找到对应代码位置写实现;写完用单元测试 + 挡板(mock 依赖对象) 做服务测试。
💡 这里能看出 DDD 的一个红利:因为有对象清单这张映射表,开发不用再猜业务逻辑该放哪——照表对号入座就行,团队协作和后期维护的认知成本大幅下降。
总结
这一讲用"在线请假考勤系统"把 DDD 从需求到代码完整走了一遍,路线是两段式:
战略设计(事件风暴)——产品愿景统一目标 → 场景分析把业务流"演"一遍(梳出命令/业务流/事件)→ 领域建模三步收敛(找实体值对象 → 找聚合根聚成聚合 → 划限界上下文)→ 综合因素拆微服务(一个限界上下文 ≈ 一个微服务)。
战术设计(微服务内部)——以命令为起点自上而下识别服务(应用服务编排、领域服务/实体执行)→ 设计聚合内的实体与值对象 → 列出"领域对象 ↔ 包/类/方法"映射清单 → 照清单落成分层代码结构(一个聚合一个隔离的目录)。
贯穿全程的两条主线:① 命令是起点(场景里的命令 → 实体的聚集 → 服务的识别都从它出发);② 一切向领域模型收敛、再从领域模型向代码映射——模型既向上定微服务边界,又向下定实体设计,最后那张映射表把业务和代码缝在一起。
一句话速记
战略设计用事件风暴把需求收敛成"实体→聚合→限界上下文→微服务",战术设计再从命令出发把每个微服务拆成"应用层编排 + 领域层执行"并一一映射到包类方法——DDD 全流程就是一条从业务语言到代码结构的收敛流水线。
思考题
你这学期或项目里要设计一个系统(比如一个带权限审批的安全管理平台:用户提工单申请某项权限 → 按敏感级别和角色逐级审批 → 通过后下发权限并留审计日志),试着照这一讲的流程走一遍:
- 场景分析:把"权限申请 → 逐级审批 → 下发 + 审计"的旅程拆成命令 / 业务流 / 领域事件三行,找出哪些是要异步处理的事件(比如"权限已下发"要不要异步触发审计日志和告警)。
- 聚合划分:你会把"权限工单"和"审计日志"放进同一个聚合,还是拆开?审计日志这种"只追加、无聚合根"的弱模型,是不是和本讲的"考勤聚合"同款处理方式?
- 边界与依赖:审批用到的"角色/权限"数据,是放进本微服务自己存,还是像本讲"从权限微服务取信息"那样做成外部依赖?两种做法在耦合度、数据一致性、维护成本上各有什么取舍?哪条逻辑该放应用层编排、哪条业务规则必须落在领域层(而不是写在 Controller 里)?











