一个简单例子:贫血模型or领域模型
最近taowen同学连续发起了两起关于贫血模型和领域模型的讨论,引起了大家的广泛热烈的讨论,但是讨论(或者说是争论)的结果到底怎样,我想值得商榷。问题是大家对贫血模型和领域模型都有自己的看法,如果没有对此达到概念上的共识,那么讨论的结果应该可想而知,讨论的收获也是有的,至少知道了分歧的存在。为了使问题具有确定性,我想从一个简单例子着手,用我对贫血模型和领域模型的概念来分别实现例子。至于我的理解对与否,大家可以做评判,至少有个可以评判的标准在这。
一个例子
我要举的是一个银行转帐的例子,又是一个被用滥了的例子。但即使这个例子也不是自己想出来的,而是剽窃的<<POJOs in Action>>中的例子,原谅我可怜的想像力
。当钱从一个帐户转到另一个帐户时,转帐的金额不能超过第一个帐户的存款余额,余额总数不能变,钱只是从一个账户流向另一个帐户,因此它们必须在一个事务内完成,每次事务成功完成都要记录此次转帐事务,这是所有的规则。
?
?
贫血模型
我们首先用贫血模型来实现。所谓贫血模型就是模型对象之间存在完整的关联(可能存在多余的关联),但是对象除了get和set方外外几乎就没有其它的方法,整个对象充当的就是一个数据容器,用C语言的话来说就是一个结构体,所有的业务方法都在一个无状态的Service类中实现,Service类仅仅包含一些行为。这是Java Web程序采用的最常用开发模型,你可能采用的就是这种方法,虽然可能不知道它有个“贫血模型”的称号,这要多亏Martin Flower(这个家伙惯会发明术语!)。
?
包结构
在讨论具体的实现之前,我们先来看来贫血模型的包结构,以便对此有个大概的了解。
?
贫血模型的实现一般包括如下包:
代码实现
先看model包的两个类,Account和TransferTransaction对象,分别代表帐户和一次转账事务。由于它们不包含业务逻辑,就是一个普通的Java Bean,下面的代码省略了get和set方法。
?
代码实现
?
现在来看实现,照例先看model中的对象:
TCP/IP分层软件分层表示层负责向用户显示信息。应用层?
?
Repository接口属于领域层
?
可能有人会将Repository接口,相当于贫血模型中的DAO接口,归于基础设施层,毕竟在贫血模型中DAO是和它的实现放在一起。这就涉及Repository接口到底和谁比较密切?应该和domain层比较密切,因为Repository接口是由domain层来定义的。用TCP/IP来类比,网际层支持标准以太网、令牌环等网络接口,支持接口是在网际层中定义的,没有在网际层定义的网络接口是不能被网际层访问的。那么为什么在贫血模型中DAO的接口没有放在model包中,这是因为贫血模型中DAO的接口是由service来定义的,但是为什么DAO接口也没有放在service包中,我无法解释,按照我的观点DAO接口放在service包中要更好一些,将DAO接口放在dao包或许有名称上对应的考虑。对于领域模型,将Repository接口放入infrastructure包中会引入包的循环依赖,Repository依赖Domain,Domain依赖Repository。然而对于贫血模型,将DAO接口放入dao包中则不会引入包循环依赖,只有service对DAO和model的依赖,而没有反方向的依赖,这也导致service包很不稳定,service又正是放置业务逻辑的地方。JDepend这个工具可以检测包的依赖关系。
?
贫血模型中Facade有何用?
?
我以前的做一个项目使用的就是贫血模型,使用了service和facade,当我们讨论service和facade有什么区别时,很少有人清楚,最终结果facade就是一个空壳,它除了将方法实现委托给相应的service方法,不做任何事,它们的接口中的方法都一样。Facade应该是主要充当远程访问的门面,这在EJB时代相当普遍,自从Rod Johson叫嚷without EJB之后,大家对EJB的热情降了很多,对许多使用贫血模型的应用程序来说,facade是没有必要的。贫血模型中的service在本质上属于应用层的东西。当然如果确实需要提供远程访问,那么远程Facade(或许叫做Remote Service更好)也是很有用的,但是它仍然属于应用层,只不过在技术层面上将它的实现委托给对应的Service。下图是贫血模型的分层:
?
?
从上面的分层可以看出贫血模型实际上相当于取消掉了领域层,因为领域层并没有包含业务逻辑。
?
?
DAO到底有没有必要?
?
贫血模型中的DAO或领域模型中的Repository到底有没有必要?有人认为DAO或者说Repository是充血模型的大敌,对此我无论如何也不赞同。DAO或Repository是负责持久化逻辑的,如果取消掉DAO或Repository,将持久化逻辑直接写入到model对象中,势必造成model对象承担不必要的职责。虽然现在的ORM框架已经做得很好了,持久化逻辑还是需要大量的代码,持久化逻辑的掺入会使model中的业务逻辑变得模糊。允许去掉DAO的一个必要条件就是Java的的持久化框架必须足够先进,持久化逻辑的引入不会干扰业务逻辑,我认为这在很长一段时间内将无法做到。在rails中能够将DAO去掉的原因就是rail中实现持久化逻辑的代码很简洁直观,这也与ruby的表达能力强有关系。DAO的另外一个好处隔离数据库,这可以支持多个数据库,甚至可以支持文件存储。基于DAO的这些优点,我认为,即使将来Java的持久化框架做得足够优秀,使用DAO将持久化逻辑从业务逻辑中分离开来还是十分必要的,况且它们本身就应该分离。
?
?
?
结束语
?
在这篇文章里,我使用了一个转帐例子来描述领域模型和贫血模型的不同,实现代码可以从附件中下载,我推荐你看下附件代码,这会对领域模型和贫血模型有个更清楚的认识。我谈到了软件的分层,以及贫血模型和领域模型的实现又是怎样对应到这些层上去的,最后是对DAO(或Repository)的讨论。以上只是我个人观点,如有不同意见欢迎指出。
?
instanceMap=new Cucst().listMap(20);
会不会造成new Cust()冗余呢?
55 楼 C_J 2009-09-27 我就是觉得别扭才提出了那个问题;
所以这里又牵涉的另一个决策问题,就是到底哪些方法放到模型里面去?
其他行为又被安置到哪里呢? 不还是得封装出一个类似DAO的层面么? 56 楼 ideal46 2009-09-28 过来学习的~!~ 57 楼 timshaw9791 2009-09-28 C_J 写道我就是觉得别扭才提出了那个问题;
所以这里又牵涉的另一个决策问题,就是到底哪些方法放到模型里面去?
其他行为又被安置到哪里呢? 不还是得封装出一个类似DAO的层面么?
建议这样来理解:“模型里面”不仅仅是Java的那个pojo,xxxService也是模型的一部分。
58 楼 C_J 2009-09-28 终归还是没有解决部分xxxService放置的问题...
放到领域?放入DAO?或者放入Service层???
所以领域模型 我觉得还是值得深思的...
也许贫血的领域模型也许更适合实际情况... 59 楼 timshaw9791 2009-09-28 C_J 写道终归还是没有解决部分xxxService放置的问题...
放到领域?放入DAO?或者放入Service层???
所以领域模型 我觉得还是值得深思的...
也许贫血的领域模型也许更适合实际情况...
想啥呢,本来就是领域的一部分,
60 楼 java_mike 2009-09-29 收藏起来,有空慢慢研究。。。 感谢分享! 61 楼 C_J 2009-09-29 timshaw9791 写道C_J 写道终归还是没有解决部分xxxService放置的问题...
放到领域?放入DAO?或者放入Service层???
所以领域模型 我觉得还是值得深思的...
也许贫血的领域模型也许更适合实际情况...
想啥呢,本来就是领域的一部分,
恩,对领域模型理解还是有点偏差~ 62 楼 jerrycong 2009-10-19 <div class="quote_title">lifethinker 写道</div>
<div class="quote_div">
<p>?</p>
<p><span style="font-size: large;"><strong>领域模型</strong>
</span>
</p>
<p>?</p>
<p>?</p>
<p>?</p>
<p><strong><span style="font-size: small;">优缺点</span>
</strong>
</p>
<p>?</p>
<p>其优点是:</p>
<ol>
<li>领域模型采用OO设计,通过将职责分配到相应的模型对象或Service,可以很好的组织业务逻辑,当业务变得复杂时,领域模型显出巨大的优势。</li>
<li>当需要多个UI接口时,领域模型可以重用,并且业务逻辑只在领域层中出现,这使得很容易对多个UI接口保持业务逻辑的一致(从领域模型的分层图可以看得更清楚)。<br>
</li>
</ol>
<p>其缺点是:</p>
<ol>
<li>对程序员的要求较高,初学者对这种将职责分配到多个协作对象中的方式感到极不适应。</li>
<li>领域驱动建模要求对领域模型完整而透彻的了解,只给出一个用例的实现步骤是无法得到领域模型的,这需要和领域专家的充分讨论。错误的领域模型对项目的危害非常之大,而实现一个好的领域模型非常困难。</li>
<li>对于简单的软件,使用领域模型,显得有些杀鸡用牛刀了。</li>
</ol>
<p>?</p>
<p>?</p>
<p>?</p>
<p><span style="font-size: small;"><strong>Repository接口属于领域层</strong>
</span>
</p>
<p>?</p>
<p>可能有人会将Repository接口,相当于贫血模型中的DAO接口,归于基础设施层,毕竟在贫血模型中DAO是和它的实现放在一起。这就涉及Repository
接口到底和谁比较密切?应该和domain层比较密切,因为Repository接口是由domain层来定义的。用TCP/IP来类比,网际层支持标准以太网、令牌环等网络接口,支持接口是在网际层中定义的,没有在网际层定义的网络接口是不能被网际层访问的。那么为什么在贫血模型中DAO的接口没有放在model包中,这是因为贫血模型中DAO的接口是由service来定义的,但是为什么DAO接口也没有放在service包中,我无法解释,按照我的观点DAO接口放在service包中要更好一些,将DAO接口放在dao包或许有名称上对应的考虑。对于领域模型,将Repository接口放入infrastructure包中会引入包的循环依赖,Repository依赖Domain,Domain依赖Repository。然而对于贫血模型,将DAO接口放入dao包中则不会引入包循环依赖,只有service对DAO和model的依赖,而没有反方向的依赖,这也导致service包很不稳定,service又正是放置业务逻辑的地方。JDepend这个工具可以检测包的依赖关系。</p>
<p>?</p>
<p>?</p>
<p>?</p>
<p><span style="font-size: medium;"><strong>DAO到底有没有必要?</strong>
</span>
</p>
<p>?</p>
<p>贫血模型中的DAO或领域模型中的Repository到底有没有必要?有人认为DAO或者说Repository是充血模型的大敌,对此我无论如何也不赞同。DAO或Repository是负责持久化逻辑的,如果取消掉DAO或Repository,将持久化逻辑直接写入到model对象中,势必造成model对象承担不必要的职责。虽然现在的ORM框架已经做得很好了,持久化逻辑还是需要大量的代码,持久化逻辑的掺入会使model中的业务逻辑变得模糊。允许去掉DAO的一个必要条件就是Java的的持久化框架必须足够先进,持久化逻辑的引入不会干扰业务逻辑,我认为这在很长一段时间内将无法做到。在rails中能够将DAO去掉的原因就是rail中实现持久化逻辑的代码很简洁直观,这也与ruby的表达能力强有关系。DAO的另外一个好处隔离数据库,这可以支持多个数据库,甚至可以支持文件存储。基于DAO的这些优点,我认为,即使将来Java的持久化框架做得足够优秀,使用DAO将持久化逻辑从业务逻辑中分离开来还是十分必要的,况且它们本身就应该分离。</p>
<p>?</p>
<p>?</p>
<p>?</p>
</div>
<p>领域模型确实一个比较好的概念, 特别是解决贫血模型里面service越来越重的业务逻辑,以及业务的重用. 但是确实对于使用原来贫血模型的人是个考验,其中一个问题就是业务的划分, 究竟哪些业务该放在model里面,哪些该放在service里面,是要好好考虑的.</p>
<p> 有些问题请教一下:</p>
<ol>
<li>按我的理解,model对象是可以调用repository里面的操作的, model对象应该是不可以调用其它model对象(如果调用会造成service被架空)</li>
<li>看到你列的领域模型的目录结构, domain里面并没有dao, 你的意思是dao这层不需要, 由repository直接调用infrastructure? dao 有封装数据库操作的作用, 我觉得看起来还是要的</li>
<li>领域模型里面的facade也有作用么,看起来它也只是调用一下domain里面service啊</li>
</ol> 63 楼 jiangmin 2009-10-20 对于这些问题可以去看一看我写的文章,从重构的角度去解释贫血和领域的好坏:
http://jiangmin.iteye.com/blog/497097 64 楼 ehongwei007 2009-11-26 fnet 写道越来越觉得DAO是没必要的
通常情况下DAO代码非常少,如果是hibernate,很多时候就一句代码
return getHibernateTemplate().xxxxxx.xxxxx;
为了一句这种代码的封装加一层有点觉得得不偿失,而且对HibernateTemplate再封装之后,代码更少。
多加一层增加了系统的复杂度,无论是维护还是调试都会麻烦一些。
省去了DAO层之后,我们发现,业务Bean更像一个可重用的组件,与其他层没什么依赖。
DAO还是非常有必要的,他是对数据持久访问的封装,如果涉及到持久层的变动(Hibernate->iBatis)就会体现出它的价值了。
