首页/开发/代码质量与评审速查/贫血模型与充血模型(Anemic vs Rich Domain)
设计原则与模式62 条

贫血模型与充血模型(Anemic vs Rich Domain)

贫血模型把数据与行为分离,业务逻辑集中在服务层;充血模型让领域对象自带行为,服务只做编排。选择取决于团队共识与复杂度:简单 CRUD 贫血够用,复杂规则充血更稳。

贫血模型与充血模型是什么

贫血模型把数据与行为分开:实体只有字段与访问器,业务逻辑集中在服务层。充血模型让领域对象自带行为,服务层只做编排与事务边界。两者都不是错误——简单 CRUD 用贫血更直白,规则密集的领域用充血更稳。真正的风险是「一边贫血一边指望对象保证一致性」,规则于是散落在多个服务里。

基本信息

贫血模型数据与行为分离,逻辑在 Service 层
充血模型领域对象自带行为,服务只做编排
判断依据规则复杂度、团队共识、是否需要强一致性约束
风险点两种风格在同一模块混用,规则归属不清

选择与落地

  • 表单式增删改查:贫血模型足够,不必强行引入领域对象
  • 规则密集(计费、审批、风控):把规则收进领域对象,避免在多处服务里复制
  • 领域对象内只放业务不变量,不要塞进 I/O 与事务
  • 服务层负责取数、调用领域方法、提交事务与发事件
  • 需要跨聚合一致性的场景,用领域事件与显式补偿而不是共享可变状态
  • 两种风格并存时明确边界:哪些模块贫血、哪些充血,写进团队约定

对比

维度贫血模型充血模型
逻辑位置Service 层领域对象内
上手成本低,结构直白中,需要领域建模
规则集中易散落重复封装在对象内
测试方式依赖服务与桩对象单测即可覆盖规则
适合CRUD、简单流程规则密集的领域

充血示例

充血示例
class Order {
    private final List<Item> items = new ArrayList<>();
    private Status status = Status.DRAFT;

    void cancel() {
        if (status == Status.SHIPPED) {
            throw new IllegalStateException("已发货订单不可取消");
        }
        status = Status.CANCELLED;
    }

    void addItem(Item item) {
        if (status != Status.DRAFT) throw new IllegalStateException();
        items.add(item);
    }
}
充血不等于把一切都放进实体:取数、发消息、开事务属于应用层职责。判断标准是「这条规则是否属于对象自身的一致性问题」——属于就放进去,需要外部协作就留在服务。
贫血到底的团队常出现同一个规则在多个 Service 复制:一处长度校验写三遍,改一次漏一处。此时不必全面改造,先把重复规则收敛到一个领域服务里就能明显降低改动成本。

易错点

  • 盲目充血:把 I/O 与事务也塞进实体,污染领域层,对象难以单测
  • 贫血到底:同一规则在多个 Service 复制,改一次漏一处
  • 两种风格在同一模块混用:规则归属不清,评审时争不出结论
  • 为充血而调用外部系统:领域对象依赖仓储或网关,测试必须带环境

数据来源与参考

  • Martin Fowler · AnemicDomainModel 模式条目;Eric Evans《领域驱动设计》
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌