贫血模型与事务脚本
用途:判断某个模块到底该不该建模时查阅——这是 DDD 里最常被忽略的“反面选项”
定义
「定义」小节链接贫血模型(Anemic Domain Model)指领域对象只承载数据、不含行为,业务逻辑全部位于服务层。术语由 Martin Fowler 提出,用于批评把对象退化为数据结构的普遍做法。
事务脚本(Transaction Script)是与之配套的另一种组织方式:每个用例写成一段面向过程的流程,按顺序完成校验、计算与存储。Fowler 在《企业应用架构模式》中把它列为合法的一等架构风格,而非“低级形态”。
为什么它常常是对的
「为什么它常常是对的」小节链接规则简单、流程直白、变更不频繁时,事务脚本的优势明显:代码线性可读、上手快、没有对象间跳转成本、改一处即可。此时引入聚合与规范只会增加间接层,让简单需求变复杂。
判断依据是业务复杂度,不是团队偏好:
| 信号 | 建议 |
|---|---|
| 规则以“字段校验 + 一次写入”为主 | 事务脚本 |
| 同一规则被 3 个以上入口复用 | 考虑上收为模型 |
| 存在跨对象不变式(合计、额度、状态约束) | 需要聚合 |
| 状态迁移复杂且常变 | 需要状态机 + 聚合 |
| 规则每月都在改,且来自业务策略 | 建模收益高 |
贫血的真实代价
「贫血的真实代价」小节链接当规则复杂却仍用贫血写法时,会出现可观察的症状:
- 规则无守护点:任何调用方都能绕过校验直接改字段,正确性依赖“大家都记得调那个 Service 方法”。
- 重复与漂移:同一条判断在页面校验、提交接口、定时任务里各写一遍,改一处漏两处。
- 测试成本倒挂:测一条规则要起数据库与远程依赖,于是干脆不测。
- 知识流失:业务含义只存在于代码分支和少数人记忆里。
从贫血走向模型的渐进路径
「从贫血走向模型的渐进路径」小节链接不需要一次性重写,按收益排序:
- 先把状态迁移收进对象(最高频缺陷来源),加迁移表与守卫。
- 再把跨字段不变式收进构造/工厂与变更方法(合计、额度、有效期)。
- 抽出可复用判定为规范或领域服务,消除重复。
- 最后清理服务层:只保留编排、事务与装配。
每步都补一条“绕过即失败”的测试,防止后续开发退回旧写法。
常见误区
「常见误区」小节链接| 误区 | 修正 |
|---|---|
| 把贫血当原罪一律重构 | 先判断复杂度,简单模块保持简单 |
| 只加 getter/setter 的“半吊子模型” | 行为与规则一起上收,否则只是换目录 |
| 服务层仍直接改字段 | 用架构测试或封装约束访问路径 |
| 重构无测试兜底 | 先补特征测试(characterization test)固定现有行为 |
与其他概念的关系
「与其他概念的关系」小节链接- 聚合与聚合根:模型化的目标是让规则有归属。
- 领域服务与应用服务:区分“规则”与“编排”是重构的核心动作。
- 分层架构:贫血问题常与依赖方向混乱同时出现。
延伸阅读
「延伸阅读」小节链接- Martin Fowler《企业应用架构模式》(Transaction Script、Anemic Domain Model 词条)
- Martin Fowler 文章《AnemicDomainModel》
- Eric Evans《领域驱动设计》第 5、6 章(模型驱动设计的对立面的讨论)