跳转到内容

贫血模型与事务脚本

用途:判断某个模块到底该不该建模时查阅——这是 DDD 里最常被忽略的“反面选项”

贫血模型(Anemic Domain Model)指领域对象只承载数据、不含行为,业务逻辑全部位于服务层。术语由 Martin Fowler 提出,用于批评把对象退化为数据结构的普遍做法。

事务脚本(Transaction Script)是与之配套的另一种组织方式:每个用例写成一段面向过程的流程,按顺序完成校验、计算与存储。Fowler 在《企业应用架构模式》中把它列为合法的一等架构风格,而非“低级形态”。

规则简单、流程直白、变更不频繁时,事务脚本的优势明显:代码线性可读、上手快、没有对象间跳转成本、改一处即可。此时引入聚合与规范只会增加间接层,让简单需求变复杂。

判断依据是业务复杂度,不是团队偏好:

信号 建议
规则以“字段校验 + 一次写入”为主 事务脚本
同一规则被 3 个以上入口复用 考虑上收为模型
存在跨对象不变式(合计、额度、状态约束) 需要聚合
状态迁移复杂且常变 需要状态机 + 聚合
规则每月都在改,且来自业务策略 建模收益高

当规则复杂却仍用贫血写法时,会出现可观察的症状:

  1. 规则无守护点:任何调用方都能绕过校验直接改字段,正确性依赖“大家都记得调那个 Service 方法”。
  2. 重复与漂移:同一条判断在页面校验、提交接口、定时任务里各写一遍,改一处漏两处。
  3. 测试成本倒挂:测一条规则要起数据库与远程依赖,于是干脆不测。
  4. 知识流失:业务含义只存在于代码分支和少数人记忆里。

从贫血走向模型的渐进路径

「从贫血走向模型的渐进路径」小节链接

不需要一次性重写,按收益排序:

  1. 先把状态迁移收进对象(最高频缺陷来源),加迁移表与守卫。
  2. 再把跨字段不变式收进构造/工厂与变更方法(合计、额度、有效期)。
  3. 抽出可复用判定为规范或领域服务,消除重复。
  4. 最后清理服务层:只保留编排、事务与装配。

每步都补一条“绕过即失败”的测试,防止后续开发退回旧写法。

误区 修正
把贫血当原罪一律重构 先判断复杂度,简单模块保持简单
只加 getter/setter 的“半吊子模型” 行为与规则一起上收,否则只是换目录
服务层仍直接改字段 用架构测试或封装约束访问路径
重构无测试兜底 先补特征测试(characterization test)固定现有行为
  • 聚合与聚合根:模型化的目标是让规则有归属。
  • 领域服务与应用服务:区分“规则”与“编排”是重构的核心动作。
  • 分层架构:贫血问题常与依赖方向混乱同时出现。
  • Martin Fowler《企业应用架构模式》(Transaction Script、Anemic Domain Model 词条)
  • Martin Fowler 文章《AnemicDomainModel》
  • Eric Evans《领域驱动设计》第 5、6 章(模型驱动设计的对立面的讨论)