01 DDD 小传

DDD(Domain-Driven Design):Eric Evans 2003 年提出,一套用于开发复杂软件的方法论与思想,核心是领域建模。常被概括成"把面向对象做对(OO Done Right)"。

传统 OO 在企业应用里的痛 → DDD 的回应

痛点DDD 的回应
重技术轻业务通用语言(业务 / 开发同一套词)
领域建模无章法模型驱动设计、限界上下文
业务 / 技术协作不足事件风暴、协作产出统一语言
难以适应频繁变化柔性设计 + 持续重构,避免过度设计

为什么近几年才"出圈"

  • 数字化推高复杂度与变化频率。
  • 敏捷 / DevOps 把"小步演进"做成日常。
  • Spring Boot、微服务、整洁架构、事件驱动、CQRS 等生态成熟,DDD 不再是"屠龙术"。

02 案例需求:企业工时系统

要做的 SaaS:员工每周填报"在哪些项目上花了多少时间"。

需求核心对象 / 范围主要功能 (CRUD / 流程)关键规则 / 约束关键字段 / 记录
多租户租户(企业)管理多个租户每个租户代表一个使用 SaaS 的企业
人员与组织管理部门、员工部门:增删改查;员工:增删改查;员工分配部门员工只能从属于一个部门部门层级示例:开发中心 → 开发组;职能部门(人事 / 财务等)
项目管理客户、合同、项目客户:增删改查;合同:增删改查 + 开始 / 结束;项目:增删改查 + 开始 / 结束客户对应客户经理;合同对应销售;项目对应项目经理;合同下可有多个项目合同 / 项目开始时间、结束时间等
人员分配项目成员关系为项目分配人员;人员退出项目项目可多人;员工可同时参与多个项目;需记录投入比例投入百分比(人 - 项目)
工时登记工时(周报 / 日记录)员工每周填报工时;查询;修改;填写备注仅当员工已分配到项目后,才可在该项目报工时日期、项目、投入时长、备注

image

03 事件风暴:识别领域事件

image

领域事件是什么

领域事件(Domain Event):业务流程某一步完成后,引发并被业务关心的"结果"。

命名约定:完成时 + 被动语态,优先采用业务术语本身(如「合同已签订」「工时已提交」)。

image

两类常见误判

不是领域事件为什么
技术事件强调系统内部行为(如「缓存已刷新」),不属业务
查询行为不改变业务状态

判定标准——满足任一即可:

  • 对某事物产生了影响
  • 该影响是业务要记录或追踪的。
  • 或它触发了对外通知(其他人 / 系统)。

识别方法:发散 → 收敛

  1. 个人发散:参与者各自写出自己理解的事件,互不干扰。
  2. 集体收敛:讨论、对齐含义、合并同类项、澄清歧义。

反复迭代,本质是结构化的头脑风暴。

副产品:统一语言(Ubiquitous Language)

事件风暴贴贴纸不是终点,协作产出的共识词汇才是。统一语言的三条:

  • 业务与开发使用同一套词。
  • 同一个词对所有人指同一概念。
  • 语言一致 ↔ 领域理解一致。

它贯穿 DDD 全过程,不是某个阶段的交付物。

04 事件风暴:识别命令与领域名词

命令:从事件反推

命令 = 引发领域事件的操作。

反推链路:先有事件 → 问"是谁做了什么才让它发生"。

每个命令还要捎带补两类上下文:

上下文问题
执行者谁发起?用户、岗位、系统对象、还是权限角色
前置查询执行前需要哪些数据用于校验 / 决策 / 填表

领域名词:从贴纸里提名词

从命令、事件、执行者、查询数据中抽出名词性概念:

来源例子提取的名词
命令签订合同合同
领域事件合同已签订合同
查询查询客户经理客户经理

领域规则要落表,不能只在便利贴上

领域规则是核心资产,便利贴 / 白板照片有三宗罪:

  • 难长期保存
  • 难版本化
  • 难持续更新

可行做法:建一张领域规则表,从事件风暴起持续往里加,与领域模型共同维护。

05 领域建模实践(上)

从事件风暴产出的领域名词起手,按四步推进:

  1. 名词暂定为实体——先不纠结对错,全量入模。
  2. 梳理关联类型——1:1 / 1:N / M:N。
  3. 抽象提炼隐含实体——发现不在贴纸里、但业务存在的概念。
  4. 补注释与约束——约束必须落到代码或数据库,并纳入「业务规则表」。

Image

06 领域建模实践(下)

继续在上篇的模型上深化:

  • 多对多用关联实体拆成两个一对多(如「员工 M:N 项目」拆出「成员分配」实体,承载投入比例)。
  • 给实体补业务操作,让模型从"数据结构"长出"行为"。
  • 模型复杂时按模块拆分提升可理解性。

落地 DDD 的两件关键事:

实践作用
补齐业务规则让模型可执行,规则不被遗忘
建立词汇表统一语言落地,团队认知一致

Image

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 DAORepository 以聚合为单位(入门可按"一个实体一个 Repo"理解);DAO 以为单位

10 代码实现:贫血还是充血

贫血 / 充血的概念与对照已在 设计模式之美 §11~§12 详述。一句话回顾:贫血 = 数据 / 行为分离(偏面向过程);充血 = 数据 + 行为聚合(贴近 OO)。企业实践常混用,不必非黑即白。

本节补 DDD 视角的三条工程要点:

  1. 代码 ↔ 模型一致:发现实现与模型不符,改模型而非绕过模型。
  2. DTO 放应用层:领域层不依赖 DTO,避免接口层数据污染领域语义。
  3. 依赖倒置:让适配器层依赖领域层定义的接口,而不是反过来。

别把两种"模型"混为一谈

模型类型受众用途
领域模型业务 + 开发表达业务概念、规则、关系
设计模型(UML 类图)仅开发表达工程内部结构、技术细节

跟领域专家讨论时聊领域模型;内部 review 时再上设计模型。

11 代码实现:领域对象 + 领域逻辑

表意接口(Intention-Revealing Interfaces)

命名要承载领域知识:方法名读起来像业务语言,而不是技术术语。

常见坏味道:

  • 函数过长。
  • 注释过多(注释在代偿命名不到位)。

工具:抽取函数重构,把"代码读不出意图"的段落抽成命名清晰的小函数。

领域逻辑 vs 应用逻辑

判据:是否包含领域专家关心的领域知识?

类型例子该放哪
领域逻辑“工时累计超过 60 小时需审批”领域对象优先;不合适放对象则放领域服务
应用逻辑接 HTTP、组合事务、发消息、写日志应用层

复杂对象的创建

  • Factory:参数少时直接调工厂方法。
  • Factory + Builder:参数多时用 Builder 链式装配,再交给 Factory 收尾。

模块划分:横切 + 纵切

切法依据例子
横切按性质 / 层controller / service / repository
纵切按业务 / 耦合order / payment / inventory

先横后竖:先把分层架构搭起来,再按业务垂直切,避免一上来就过度模块化。

– EOF –