充血模型 :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层负责。充血模型的判断标准不是方法有多复杂而是数据是否自给自足。数据在自己身上多复杂都该放进来数据在别人身上多简单都不该放。