领域驱动设计
从业务语言和领域规则出发,用实体、值对象、聚合与上下文边界组织代码,并用可复现的实验检验每一个边界是否守得住。
从哪里开始
先读设计专题里的 从统一语言到限界上下文,它讲清了子域、限界上下文和防腐层。然后按顺序读本专题:事件风暴 和 领域建模 把业务讨论变成规则和模型;实体与值对象、聚合边界 和 分层 把模型放进代码;边界不止一个、持久化 和 上下文集成 处理模型与数据库、与其他上下文、与部署单元的关系;CQRS 按需阅读;最后在 精益切片与遗留改造 里看怎样在真实项目里开始。
这个专题讲什么
很多项目说自己用了 DDD,证据是目录里有 domain、application、infrastructure,类名里有 Repository 和 AggregateRoot。顺着一个需求走下去,业务规则仍然散在控制器、服务、SQL 和定时任务里;同一个词在不同团队含义不同;表结构一改,领域对象跟着改;并发一来,规则被绕开。
这个专题关心的是知识进入代码的路径:业务人员和开发人员先用同一套语言描述问题,再把必须同时成立的规则放进明确的边界,最后用测试、事务和架构规则证明这些边界没有被绕过。分层、仓储、领域事件和 CQRS 都是可选的手段;统一语言、边界和规则的一致性才是主线。
统一案例
所有文章使用同一个虚构的活动报名平台,每篇只截取其中一个问题:
| 上下文 | 负责什么 | 核心概念 |
|---|---|---|
| 活动 | 发布活动和场次,设定容量 | 活动、场次、容量 |
| 报名 | 报名、候补、取消与递补 | 场次聚合、报名、候补、参会人 |
| 计费 | 应收、退款 | 应收、付款人、金额 |
| 通知 | 模板、渠道、投递 | 通知、投递记录 |
报名是核心域,其余三个是支撑域或通用域。各篇的实验都在 codesphere-labs/ddd 中,可以用 make verify 复现。
什么时候不需要
以增删改查为主、规则很少、上线后很少变化的模块,简单的分层和事务脚本就够了。DDD 的结构用来管理复杂性,复杂性不在那里时,结构本身就是负担。
文章
01事件风暴从过去式的事件反推命令、执行者与策略,把热点定成规则;初稿模型漏掉的规则让 3 个验收测试失败→02用例子把规则谈清楚抽象规则在具体例子上暴露分歧;反例、给定—当—那么、可执行规格的维护→03领域建模名词搬运模型缺了什么:用业务问题确认约束、把操作放进模型、分类先看规则,词汇表与规则表可追踪→04几个模型都说得通时新需求表达不了时列候选(含不改模型),用业务例子表比较,淘汰理由具体到例子,决策记录写下可观察的复评条件→05候选、确认与生效抽取出的金额与训练出的模型都是候选:来源哈希、版本条件、幂等回执与尝试号,回退是一次新的切换→06子类型、类型字段还是组合五个判据:取值还是规则、可替换性、封闭分类、独立维度、被引用时的身份;default 分支两处处理相反,(type, id) 引用 2 条悬空→07组织结构怎样建模知识层规定能成立的关系,改规则是一次迁移;结构线与有效期;并发加边在 READ COMMITTED 下成环,SERIALIZABLE 与锁结构线只成功一个→08实体与值对象按身份还是按值相等;BigDecimal 精度、record 浅不可变、保存时才分配 id 的实测问题→09构造器里写了校验,对象就一定合法吗JSON 经无参构造器写入私有字段、改过的序列化字节、反射写字段、交出的内部集合与可变元素、父类公共 setter,七个入口哪些绕过了不变量→10「同一时段不能重叠」守在哪里跨行规则的四个位置:唯一键表达不了区间、先查后插两条都提交、锁住聚合根只留一条、PostgreSQL 排他约束与半开区间→11请求体不是领域对象字段由谁决定是一条规则:直接绑定实体时客户端写入了租户与状态,命令对象与可信上下文、按(租户,id)查询、表单允许字段→12计划、发生与记录三个时间updatedAt 当支付时间晚了一天、计划存成 Instant 在夏令时后提前一小时、迟到的读数按哪天汇总、取消按哪个时间判断→13聚合边界不变量决定边界:候补拆成独立聚合后新报名全部插队,以活动为根的锁等待是以场次为根的十几倍→14领域模型里的集合,背后是一次查询Hibernate 7.4 实测:size() 检查名额加载 1001 个对象,Set 加一项加载全部,只往 mappedBy 的 List 加一项根版本号不变,N+1 与批量删除→15同一个人,几种角色大实体撤销角色后数据仍在、历史丢失;接口视图一次转换拿到全部能力;角色对象的有效期、规则与按上下文拆分→16这个金额从哪来引用当前价格让历史订单从 230 变成 270;快照、价格版本、报价有效期与凭证链各自能回答的问题→17从模型到 HTTP 接口整体 PUT 丢失别人的修改、缺字段被清空、状态字段绕过规则、POST 重发多建订单;If-Match、PATCH、命令端点、幂等键与 202→18分层、应用服务与仓储一条用例穿过四层的实际顺序,规则放在哪里,对象转换的往返检查,把依赖方向写成测试→19事务脚本还是领域模型同一需求的两种实现经历三次变化:都通过验收;领域模型更长,事务脚本的名额条件从 1 处变成 3 处,转折点是用例开始共享规则→20DDD 的边界不止一个六种边界与模块化单体;拆出通知服务后停机、超时与重复带来的新失败,以及拆分的证据→21持久化与重建重建不是创建、版本号冲突、子表按差异保存,三种继承映射的执行计划、空间与约束→22上下文集成契约与防腐层,双写丢失与 Outbox,重复投递、乱序与毒消息的实测处理→23CQRS 不是两套系统从查询不走仓储到同步、异步读模型:同一看板 84.9ms、31.4ms 与 1.4ms,投影暂停与重放→24精益切片与遗留改造值不值得、第一个切片怎么选;字符化测试与影子比对找出两条隐含规则,按比例分流与回滚→
相关专题
- 设计原则与模式 — 关注点分离、封装与组合、状态机与事件,是本专题的基础
- Domain Driven Kit — 一个按四层结构组织的实现,文中多处与它的当前能力对照
- 分布式与高并发 — 跨库查询、订单一致性等与上下文拆分相关的问题