领域驱动设计DDD ABP VNext 三:领域模型 之 失血模型,贫血模型,充血模型,胀血模型 电脑版发表于:2022/5/22 16:56 [TOC] 领域模型分为:失血模型,贫血模型,充血模型,胀血模型。 ## 一、失血模型 传统的三层架构,实体对象就是简单的POJO或者POCO,只包含属性没有行为,也就是单纯的实体对象,是不包含任何业务逻辑的,就是失血模型 。 tn2> 该模型的缺点是不够面向对象,领域对象只是作为保存状态或者传递状态使用,它是没有生命的,只有数据没有行为的对象不是真正的对象,是失血的对象,在业务逻辑层里面处理所有的业务逻辑。 tn3>虽然简单的对象也通过了私有化字段使用getter/setter方法修改对象属性。也是属于面向对象三大特征的封装,但这还是太过失血,完全不够面向对象的设计。 事务的管理是在业务逻辑层完成的,业务逻辑层实现具体的业务逻辑,领域模型只有get,set。这种模式下会造成领域逻辑散落在各个应用服务中,导致代码重复。 ## 二、贫血模型 简单来说,就是domain ojbect(领域对象)包含了不依赖于持久化的领域逻辑,而那些依赖持久化的领域逻辑被分离到业务逻辑层。 #### 使用DDD要注意应用服务与领域服务的区别,也能更好理解贫血模型与应用 应用层起到一个编排的作用。只有应用逻辑,没有领域逻辑。 应用服务不做出任何业务决策,它将这些决策委托给领域模型。请注意,领域模型是隔离的:Atm实体不会将自身保存到数据库,也不会通过支付网关直接收费。我们在这里有一个很好的关注点分离:业务逻辑放入领域模型,而与外部世界的交互 - 应该放入应用服务。 您可以注意到大多数遵循此指南的代码库中的模式。他们的执行流程如下: - 准备业务操作所需的所有信息:从数据库加载参与实体并从其他外部源检索所需的所有数据。 - 执行操作。该操作由领域模型做出的一个或多个业务决策组成。这些决定导致更改模型的状态,或生成一些结果(上面示例中的amountWithCommission值)。 - 将操作结果应用于外界。 只有第1步和第3步涉及与外部依赖关系的工作。在第一步中检索到数据下接着实现第二步:接受的入参数及其生成的输出仅包含实体,值对象和基本类型。 请注意,在简单的CRUD应用程序中,没有第二步,因为没有做出决定的地方。在这种情况下,所有操作都可以仅由应用服务执行,无需将它们委托给领域模型。实际上,不存在任何富领域模型。在这种情况下,贫血领域模型也可以正常工作。 实体不光有数据,还要有行为。谁拥有这些数据谁就负责数据的维护,否者就会造成模型失血。 tn2>既然是面向对象,那么对象就应该不仅仅只有属性,而且应该还有包含该对象拥有的行为也就是方法,否者就是面向过程而不是面向对象。所以以数据库为核心,实体对象只有属性没有行为方法其实更多的是面向过程的开发方式,开始的时候简单到后面会越来越复杂,容易越写越乱,什么乱七糟八的业务逻辑都没有边界的往里边堆,越来越难以维护。 tn3>如果领域层的对象不包含任务业务逻辑,也就是对象仅仅就是属性没有方法,那么其实还是三层架构而已,相当于把实体单独提出来一层而已,领域对象几乎只作传输介质之用。 这种模型是Martin Fowler所指的真正的领域模型(domain model)。 #### 如何划分domain logic和service logic的标准 我们知道了这种模式,是把部分业务逻辑(不包含数据持久化的业务逻辑)从业务逻辑层(领域服务层)放到了领域模型中来,但是我们在实际拆分的时候应该遵循一些什么原则呢。 tn2>领域逻辑应该放在只和一个domain object的实例状态有关的地方,而不应该和一批domain object的状态有关的地方。 比如: tn3> 比如遇到需要两个领域对象协调的,那么则放到领域服务中处理。如:实体账户A给实体账户B转账,这种转账过程放到那个实体都不合适,那么就放到领域服务中处理。领域服务是没有状态的只有行为。 这种方式相比失血模型 ,更加的面向对象。domain ojbect(领域对象)包含了不依赖于持久化的领域逻辑,但还没有把所有业务逻辑都放到领域对象。 ## 三、充血模型 充血模型和第二种模型差不多,所不同的就是如何划分业务逻辑,即认为,绝大多业务逻辑都应该被放在domain object里面(包括持久化逻辑),而Service层应该是很薄的一层,仅仅封装事务和少量逻辑,不和DAO层打交道。 Service(事务封装) ---> domain object <---> DAO 这种模型就是把第二种模型的domain object和business object合二为一了。 **该模型的优缺点** 该模型的优点: tn2>1、更加符合OO的原则<br> 2、Service层很薄,只充当Facade的角色,不和DAO打交道。 这种模型的缺点: tn3> 1、持久化层和domain object形成了双向依赖,复杂的双向依赖会导致很多潜在的问题。不过我们依赖的是仓储,其实这块也不是什么问题了。只是架构发展的某个阶段可能会要考虑一下这种问题,这里也列一下。 <br> 2、如何划分Service层逻辑和domain层逻辑是非常含混的,在实际项目中,由于设计和开发人员的水平差异,可能导致整个结构的混乱无序。 <br> 3、考虑到Service层的事务封装特性,Service层必须对所有的domain object的逻辑提供相应的事务封装方法,其结果就是Service完全重定义一遍所有的domain logic,非常烦琐,而且Service的事务化封装其意义就等于把OO的domain logic转换为过程的Service TransactionScript。该充血模型辛辛苦苦在domain层实现的OO在Service层又变成了过程式,对于Web层程序员的角度来看,和贫血模型没有什么区别了。 ## 四、胀血模型 基于充血模型的第三个缺点,有人提出,干脆取消Service层,只剩下domain object和DAO两层,在domain object的domain logic上面封装事务。 domain object(事务封装,业务逻辑) <---> DAO 似乎ruby on rails就是这种模型,他甚至把domain object和DAO都合并了。 **该模型的优缺点** 该模型优点: tn2> 1、简化了分层 <br> 2、也算符合OO 该模型缺点: tn3>1、很多不是domain logic的service逻辑也被强行放入domain object,引起了domain ojbect模型的不稳定。<br> 2、domain object暴露给web层过多的信息,可能引起意想不到的副作用。 ## 五、使用推荐 tn2> 贫血模型和充血模型在不同的业务场景下,都能够有用武之地。但失血模型和胀血模型是属于两个极端,不推荐使用。 tn3>相比充血模型其实个人更加推荐使用贫血模型。如果把数据库持久化的操作和实体对象包含在一起,虽然更加的OO,但是需要领域对象与持久化层的双向依赖,对领域逻辑与服务逻辑的拆分需要更加大的考验,更适用于业务逻辑比较复制的系统。但是实际情况实际分析,对于一些业务逻辑不是特别复杂的项目,我们在设计上也可以不用那么聚合。其实还是要看情况,不同的设计都有它的优缺点,比如双向依赖其实我们也是依赖的仓储,其实也不是什么问题,也更加符合OO,所以还是要根据不同的系统不同的业务逻辑来针对性的选择不同的模式吧。 当然很多说到领域模型都把上面的四种模块简化成,两种模型。贫血模型和充血模型, 贫血模型指的是上面的失血模型除了属性没有任何逻辑。