DDD中的充血模型的个人理解

09-10-10 lovejdon
         

在我们应用OO进行分析设计的时候,又提出了贫血和充血的概念.并产生了很大的争论.结合DDD,在这里,我也谈谈我的想法。

1.贫血模式说白了就是把对象看成是数据的载体.因为它不存在实际的操作动作,只是各种数据的集合.从这个角度,尽管我们设计出来了对象.实质上我们只是在过程式开发模式(没有用OO语言的时候)外面包了"外套"--对象.这个对象好象是个容器,容纳了我们业务上需要处理的数据,仅此而已.所以不难想象:当我们在开发中,从需求分析到表结构确认,然后利用eclipse生成的POJO,实际上就是那些数据的载体.只是用对象给伪装了一下而已,看似OO,实则过程.

这样有一定的弊端:违反了OO的设计.对象讲究的是数据和行为的统一体,而不是分离来看.而贫血模型分离了数据和行为,领域对象作为了数据的载体,而服务对象作为了行为的集.这样我们拿面向对象的语言来做分析设计的时候就完全的抛弃了对象分析的方式.而以数据为中心进行设计.包括说的从数据库反向生成实体的方式,都是一个设计缺失的表现.

再则,我们利用spring ,hibernate进行开发的时候,产生的POJO是依托于数据库结构的.试想,这样的方式从hibernate的annotation考虑还有何意义,所谓的对象细粒度,数据库表粗粒度又如何体现?实际annotation提供的功能是我们在利用对象分析方式分析出模型后,在持久化的时候遇到的问题,hibernate给我们解决的.而不是反过来思考问题.这也恰恰曲解了hibernate的价值所在.

可能有人会说:"等依据数据库表结构生成了POJO之后,再根据POJO的情况,进行细粒度的划分,这样就可以利用annotation了".如果这样做的话,岂不是饶了个大圈又回来了,说句不好听的话叫"脱裤子放屁"啊.而我们现在的很多开发的方式,都是产生POJO后,就结束了,还没有饶圈.但是尽管不饶,方式也是错误的.如果"脱裤子放屁",那就错上加错了.这个就是典型的贫血开发.这也就说明了为什么我们会拿着OO的语言来做非OO的事情,这就是其中的原因之一.

还有一点.采用贫血开发.真正的服务,也就是我们常说的service,包含了对这些POJO的管理(CRUD),在服务里面大量充斥着管理方法.越来越庞大,随着项目的增加,越来越多的方法充斥着一个对象里面,难于管理.不光这样,实际在service里面,不光包含了我们针对一个软件应用领域的行业的规范,还包括了我们架设软件环境的基础设施的规范(事务,持久化等等),所以在一个方法里面充斥着本来属于不同层次的东西,这样的方式最终会导致混乱,难于维护是再所难免.可这恰恰是目前我们采用SSH开发软件的常规做法.

2.充血相对贫血多了点生机,有了动作了,而不是孤立的数据了.这里也和对象的概念想符合了,对象就应该是数据和行为的结合体.所以大家都会说,既然要OO的开发,就要这样去做,让数据和他们的相关行为紧密联系,而不应该分离.当然,这样的纯OO的开发思想是很不错,但是也存在一定的问题.我们的pojo如果真的是包含了数据和行为的话,必然会考虑持久化的问题.那么每一个行为都需要以事务脚本结束,也就是都必须以事务提交回滚作为终止.那么在我们持久化的时候,这些约束性的条件我们需要放置在哪里,放置在POJO里面?

如果这样做了,那么实际又是上面说的,虽然是富饶了,但是仍然将基础设施的内容与软件所属行业的规则混淆了.也会存在问题.所以我们就需要将这个层次提取出来,单独设立一层,也就是将基础设施的内容层,与业务领域的内容层分离.这样就变成了两个层次,也就是DDD中说的服务的概念,是应用层和领域层的总和.应用层只是程序级别的内容,也就是我们所说的基础设施(框架)的内容.而领域层才是业务领域里的内容的体现.领域需要依托的数据,就来自POJO(分析的实体和值对象的结果)这样的话就达到了分离的效果.但是POJO是富饶的,POJO的行为动作是领域层和应用层的总合,叫做服务,既然两者已经分离出去了,那么这个行为呆在pojo里没什么意思,索性不要了.讲到这里,实际我们不难发现DDD也是这样做的.这或许也是作者的出发点之一吧.

但是有人就说了这样的话,pojo又变穷了,没血了,说来说去,这不还是贫血吗?为什么DDD里说的领域模型是充血的而不是贫血的呢?其实这里有一点恰恰是大家忽略的问题.也就是我们思考角度的问题.

在我们没有DDD的时候,大家讨论的贫血模型里的DOMAIN OBJECT,实际上是从程序的角度来阐述的对象,而不是真正意义上的对象.他仅仅就是数据的载体而已.但是很多人都会认这样的对象是真正的对象.如果这样的数据载体也叫对象的话,那么OO的概念好象就体现不出来了.也就无所谓OO设计了.况且,我们那时候说的这个贫血对象实际有两个作用,一个用于前台页面的展示,另一个用于持久化操作.

那么DDD中说的要把逻辑放在领域对象里,看似好象和贫血模型冲突.实际上这里说的业务逻辑是领域上的逻辑,也就是业务上的验证规则,计算规则等等,并不是在贫血对象中增加一些方法当作行为这么简单的事情.领域在DDD中包括了,实体,值对象和服务.并不能单独一个实体就是一个领域对象,否则就会认为实体是贫血的,没行为可言.实际这个领域的范围更广了,是几个对象相互作用的结果.构成了领域模型,也就是实体,值对象和服务的结合体,是一个真正完整的充血模型.

问题又来了:那这么做确实是充血了,符合了OO,但是OO中说的是一个对象有属性和方法,但是这里却要几个类集合起来构成一个领域模型,看似很矛盾.

其实正如BANQ说的,分析问题的层次不一样导致了这种茫然,DDD是侧重从分析的角度思考问题,用这种分析的结果去知道你对象的设计.所以针对贫血和充血是从分析的角度考虑.是比我们程序代码层次的对象更高一个层次的设计.所以不在一个层次的东西,没有任何的可比性.也就是出发点的问题.我们常说的POJO就是一个结果,是分析后设计出来的结果.而贫血和充血是从你拿到需求的时候分析产生的.所以有的人说这个POJO是贫血的或者是充血的,这种说法所站的角度和DDD中贫血充血站的角度是不一样的.正如banq大哥说的,在分析的时候坚持完整的领域模型就可以了,分析和设计不一定都百分之百对应.

我们在设计的时候必然会考虑应用技术的环境.分析的层次不同,考虑问题的出发点就不同了.以上也是我个人的一点想法,有不正确的地方请指正.

[该贴被lovejdon于2009-10-10 14:29修改过]

[该贴被lovejdon于2009-10-10 14:29修改过]

         

1
banq
2009-10-11 08:39

是的,失血与否其实和需求模型有非常大关系。比如DDD中模型核心是一群模型对象有一个根实体,根实体一般肯定不是失血模型,因为需要存在一些行为和方法来维持其群组中元素的一致性。就象一个组织的头要维持一个组织存在,必然在组织内部实施一些行为,保持公平等等。

而群组中的元素实体,或值对象,有可能就是失血模型。

所以,当初以失血和充血武断地从屁股结果上去判断,是没有益处的,关键是要依据DDD进行实际判断。

不过,实战事情远非没有这么简单,见我的另外一篇文章,使用Domain Event避免贫血模型:

Domain Events – 救世主

[该贴被banq于2009-10-11 16:01修改过]