充血模型 :DDD中的领域对象里可以放什么方法?

📅 2026/7/25 1:56:35 👁️ 阅读次数 📝 编程学习
充血模型 :DDD中的领域对象里可以放什么方法?

DDD推崇充血模型,这个知识点很多人都知道。但充血到什么程度,每个人理解差异还蛮大的。

就我自己见过的,DDD的领域对象里的方法,反面案例有两种:

第一,只把聚合根写成纯数据容器,所有业务逻辑全扔Service里;

第二,而有人则反过来,把复杂的创建流程塞进聚合根,居然在里面调用rpc接口。

后来我这边在团队里定了一条规则:谁有数据,谁提供方法。

就是说判断一个方法该不该放在聚合根里就只看一件事:它需要的数据是不是全部在自己身上。

举个最常见的场景,很多项目里判断订单状态是否是待发货状态(假设枚举值是pending),是这么做的:从数据库查出订单对象,然后拿order.getStatus()跟pending这个固定的值做比较。

这样做当然没问题,但「pending代表待发货」这个业务知识散落在了各个调用方。哪个Service需要判断状态,就自己写一遍字符串比较。

正确的做法是让订单自己回答这个问题。status字段在订单身上,那么「是否待发货」这个判断就该由订单来提供。调用方不需要知道状态值具体是什么字符串,只需要问订单一句话,订单用自己的字段给出答案。isPending()执行时只用了this.status这一个字段,不需要查库,不需要调接口,数据完全自给自足。

就是说,你从数据库加载出订单对象后,直接调用isPending(),就知道该订单是否是待发货状态了。

简单,好用,又靠谱,且用起来还很爽。

这个模式在各种业务系统里到处都能用。做营销活动系统,活动表里有startTime和endTime两个字段。传统写法是在Service里到处用当前时间和这两个字段做比较,判断活动是否开始、是否结束。换成实体方法,isStarted()、isEnded()、isOngoing()这三个方法全部只依赖实体自身的startTime和endTime字段加上当前时间,不需要查数据库,不需要调外部接口。

这些方法可以有多复杂?想多复杂就多复杂。

哪怕判断逻辑涉及实体上十几个字段、几十行条件分支,只要不需要从外面拿数据,就属于实体的方法。方法的复杂度和它该不该放在实体里无关。

分界线始终只有一条:需不需要跟外部世界交互。聚合根是一个封闭的计算单元,给它足够的输入数据,它在内存里完成所有计算。需要从外部获取数据或者需要协调多个系统的操作,由Service层负责。

充血模型的判断标准不是方法有多复杂,而是数据是否自给自足。数据在自己身上,多复杂都该放进来;数据在别人身上,多简单都不该放。