01 DDD 小传
DDD(Domain-Driven Design):Eric Evans 2003 年提出,一套用于开发复杂软件的方法论与思想,核心是领域建模。常被概括成"把面向对象做对(OO Done Right)"。
传统 OO 在企业应用里的痛 → DDD 的回应
| 痛点 | DDD 的回应 |
|---|---|
| 重技术轻业务 | 通用语言(业务 / 开发同一套词) |
| 领域建模无章法 | 模型驱动设计、限界上下文 |
| 业务 / 技术协作不足 | 事件风暴、协作产出统一语言 |
| 难以适应频繁变化 | 柔性设计 + 持续重构,避免过度设计 |
为什么近几年才"出圈"
- 数字化推高复杂度与变化频率。
- 敏捷 / DevOps 把"小步演进"做成日常。
- Spring Boot、微服务、整洁架构、事件驱动、CQRS 等生态成熟,DDD 不再是"屠龙术"。
02 案例需求:企业工时系统
要做的 SaaS:员工每周填报"在哪些项目上花了多少时间"。
| 需求 | 核心对象 / 范围 | 主要功能 (CRUD / 流程) | 关键规则 / 约束 | 关键字段 / 记录 |
|---|---|---|---|---|
| 多租户 | 租户(企业) | 管理多个租户 | 每个租户代表一个使用 SaaS 的企业 | — |
| 人员与组织管理 | 部门、员工 | 部门:增删改查;员工:增删改查;员工分配部门 | 员工只能从属于一个部门 | 部门层级示例:开发中心 → 开发组;职能部门(人事 / 财务等) |
| 项目管理 | 客户、合同、项目 | 客户:增删改查;合同:增删改查 + 开始 / 结束;项目:增删改查 + 开始 / 结束 | 客户对应客户经理;合同对应销售;项目对应项目经理;合同下可有多个项目 | 合同 / 项目开始时间、结束时间等 |
| 人员分配 | 项目成员关系 | 为项目分配人员;人员退出项目 | 项目可多人;员工可同时参与多个项目;需记录投入比例 | 投入百分比(人 - 项目) |
| 工时登记 | 工时(周报 / 日记录) | 员工每周填报工时;查询;修改;填写备注 | 仅当员工已分配到项目后,才可在该项目报工时 | 日期、项目、投入时长、备注 |
03 事件风暴:识别领域事件
领域事件是什么
领域事件(Domain Event):业务流程某一步完成后,引发并被业务关心的"结果"。
命名约定:完成时 + 被动语态,优先采用业务术语本身(如「合同已签订」「工时已提交」)。
两类常见误判
| 不是领域事件 | 为什么 |
|---|---|
| 技术事件 | 强调系统内部行为(如「缓存已刷新」),不属业务 |
| 查询行为 | 不改变业务状态 |
判定标准——满足任一即可:
- 对某事物产生了影响。
- 该影响是业务要记录或追踪的。
- 或它触发了对外通知(其他人 / 系统)。
识别方法:发散 → 收敛
- 个人发散:参与者各自写出自己理解的事件,互不干扰。
- 集体收敛:讨论、对齐含义、合并同类项、澄清歧义。
反复迭代,本质是结构化的头脑风暴。
副产品:统一语言(Ubiquitous Language)
事件风暴贴贴纸不是终点,协作产出的共识词汇才是。统一语言的三条:
- 业务与开发使用同一套词。
- 同一个词对所有人指同一概念。
- 语言一致 ↔ 领域理解一致。
它贯穿 DDD 全过程,不是某个阶段的交付物。
04 事件风暴:识别命令与领域名词
命令:从事件反推
命令 = 引发领域事件的操作。
反推链路:先有事件 → 问"是谁做了什么才让它发生"。
每个命令还要捎带补两类上下文:
| 上下文 | 问题 |
|---|---|
| 执行者 | 谁发起?用户、岗位、系统对象、还是权限角色 |
| 前置查询 | 执行前需要哪些数据用于校验 / 决策 / 填表 |
领域名词:从贴纸里提名词
从命令、事件、执行者、查询数据中抽出名词性概念:
| 来源 | 例子 | 提取的名词 |
|---|---|---|
| 命令 | 签订合同 | 合同 |
| 领域事件 | 合同已签订 | 合同 |
| 查询 | 查询客户经理 | 客户经理 |
领域规则要落表,不能只在便利贴上
领域规则是核心资产,便利贴 / 白板照片有三宗罪:
- 难长期保存
- 难版本化
- 难持续更新
可行做法:建一张领域规则表,从事件风暴起持续往里加,与领域模型共同维护。
05 领域建模实践(上)
从事件风暴产出的领域名词起手,按四步推进:
- 名词暂定为实体——先不纠结对错,全量入模。
- 梳理关联类型——1:1 / 1:N / M:N。
- 抽象提炼隐含实体——发现不在贴纸里、但业务存在的概念。
- 补注释与约束——约束必须落到代码或数据库,并纳入「业务规则表」。
06 领域建模实践(下)
继续在上篇的模型上深化:
- 多对多用关联实体拆成两个一对多(如「员工 M:N 项目」拆出「成员分配」实体,承载投入比例)。
- 给实体补业务操作,让模型从"数据结构"长出"行为"。
- 模型复杂时按模块拆分提升可理解性。
落地 DDD 的两件关键事:
| 实践 | 作用 |
|---|---|
| 补齐业务规则 | 让模型可执行,规则不被遗忘 |
| 建立词汇表 | 统一语言落地,团队认知一致 |
07 领域建模原理
DDD 的领域模型是对领域知识的提炼抽象,同时兼顾业务表达与技术落地。
模型驱动设计的三层一致性
| 一致性 | 意味着 |
|---|---|
| 领域模型 ↔ 业务需求 | 模型反映真实业务,业务方能看懂 |
| 系统实现 ↔ 领域模型 | 代码结构与模型同形 |
| 实现 ↔ 需求(由前两者推出) | 自然落地,不偏航 |
统一语言的维护
统一语言以领域模型 + 词汇表为基础,靠持续协作维护三者的一致性:模型、语言、实现。任何一处变了,另两处必须跟着改。
08 数据库设计
三范式(3NF)速查
| 范式 | 一句话规则 | 反例 |
|---|---|---|
| 1NF | 字段必须原子,不可再分 | phones = "138xxxx, 139xxxx",应拆成多行或多列 |
| 2NF | 非主属性必须完全依赖整个主键 | 主键 (订单ID, 商品ID),订单日期 只依赖 订单ID,应拆出订单表 |
| 3NF | 非主属性不能传递依赖 | 员工ID → 部门ID → 部门名称,应拆出部门表 |
DDD 怎么指导数据库设计
| 领域模型概念 | 数据库映射 |
|---|---|
| 实体 | 表 |
| 属性 | 字段(按需补充) |
| 1:N 关联 | 外键(云上常用「虚拟外键」) |
| M:N 关联 | 关联表 |
| 统一语言 | 规范命名(表名 / 字段名) |
相较"拍脑袋"建模:
- 与业务对齐。
- 更符合范式。
- 静态数据结构与动态业务逻辑统一在一套模型里。
09 分层架构
核心原则:不稳定的依赖稳定的。 越靠内层越稳定,越靠外层越易变。
各层职责
| 层 | 职责 | 稳定度 |
|---|---|---|
| 领域层 | 封装领域数据与规则,是系统核心 | 最高 |
| 应用层 | 编排领域能力为粗粒度服务,处理事务、日志等横切关注点 | 高 |
| 适配器层 | 隔离外部交互(主动 / 被动),让内层与技术细节解耦 | 低 |
| common 层 | 工具与框架支撑 | — |
两组易混对子
| A vs B | 区别 |
|---|---|
| 主动 vs 被动适配器 | 调用方向:前者接收外部请求;后者访问外部资源 |
| Repository vs DAO | Repository 以聚合为单位(入门可按"一个实体一个 Repo"理解);DAO 以表为单位 |
10 代码实现:贫血还是充血
贫血 / 充血的概念与对照已在 设计模式之美 §11~§12 详述。一句话回顾:贫血 = 数据 / 行为分离(偏面向过程);充血 = 数据 + 行为聚合(贴近 OO)。企业实践常混用,不必非黑即白。
本节补 DDD 视角的三条工程要点:
- 代码 ↔ 模型一致:发现实现与模型不符,改模型而非绕过模型。
- DTO 放应用层:领域层不依赖 DTO,避免接口层数据污染领域语义。
- 依赖倒置:让适配器层依赖领域层定义的接口,而不是反过来。
别把两种"模型"混为一谈
| 模型类型 | 受众 | 用途 |
|---|---|---|
| 领域模型 | 业务 + 开发 | 表达业务概念、规则、关系 |
| 设计模型(UML 类图) | 仅开发 | 表达工程内部结构、技术细节 |
跟领域专家讨论时聊领域模型;内部 review 时再上设计模型。
11 代码实现:领域对象 + 领域逻辑
表意接口(Intention-Revealing Interfaces)
命名要承载领域知识:方法名读起来像业务语言,而不是技术术语。
常见坏味道:
- 函数过长。
- 注释过多(注释在代偿命名不到位)。
工具:抽取函数重构,把"代码读不出意图"的段落抽成命名清晰的小函数。
领域逻辑 vs 应用逻辑
判据:是否包含领域专家关心的领域知识?
| 类型 | 例子 | 该放哪 |
|---|---|---|
| 领域逻辑 | “工时累计超过 60 小时需审批” | 领域对象优先;不合适放对象则放领域服务 |
| 应用逻辑 | 接 HTTP、组合事务、发消息、写日志 | 应用层 |
复杂对象的创建
- Factory:参数少时直接调工厂方法。
- Factory + Builder:参数多时用 Builder 链式装配,再交给 Factory 收尾。
模块划分:横切 + 纵切
| 切法 | 依据 | 例子 |
|---|---|---|
| 横切 | 按性质 / 层 | controller / service / repository |
| 纵切 | 按业务 / 耦合 | order / payment / inventory |
先横后竖:先把分层架构搭起来,再按业务垂直切,避免一上来就过度模块化。
– EOF –