论坛首页 Java企业应用论坛

一个简单例子:贫血模型or领域模型

浏览 77066 次
该帖已经被评为精华帖
作者 正文
   发表时间:2008-12-02  
看了你的代码,有个感觉是无论是DAO或者是Resposity,都是domain logic里的噪声。如果Domain logic也能透明的调用DAO或Respoisty,这样的domain模型会更完美,而且更关注业务。
0 请登录后投票
   发表时间:2008-12-02  
DAO层当前应该存在,原因如下:
1,必尽还是关系数据库的时代,还有对于数据库访问不同数据库还是有存在差异,用DAO层实现来解决差异,除非ORM足够强大,根本不存在。
2,DAO分担业务层的逻辑(小逻辑),就如domain层实体里不光是setter,getter的原里一样。但这层逻辑业务仅针对DAO对应的domain层相关逻辑,否则建议到service层。
3,service层应该是主要业务逻辑,不关心应用逻是什么,service的逻辑接口应该永远不了,除非业务改变。个人认为我们的业务主逻辑图都在service层,再配罗dao的小逻辑,(千万不要把hibernate的HQL,或者 相关SQL在这里中写逻辑,初学者误区)。
4,DAO可以认为是大海,有无穷的资源,而service是大海上的船,是dao层上按需所取,船下的海域就是不同dao的小业务逻辑。dao与entity应该是强偶合性的,service以上是松偶合。
5,service不光要dao层,还是分布式,远程访问。就是银行转账的扩展。一般应用都是调用银行接口转账,还有相应的业务关联。转账可能不是一个事务的问题了(当然业务层的设计也可以解决,只是简单举例)
0 请登录后投票
   发表时间:2008-12-02  
正在郁闷软件分层结构呢!受益匪浅!谢谢!
0 请登录后投票
   发表时间:2008-12-03   最后修改:2008-12-03
新手问一下,不用DAO,假如我要条件查询怎么解决?
比如select * from entity as e where e.name=:name and e.id=:id
写在service里面?
0 请登录后投票
   发表时间:2008-12-03   最后修改:2008-12-05
taowen 写道


为什么要有Repository?
在我看来,与其说PublicationRepository,不如说Publications。Repository是一个集合对象,它封装了集合的逻辑。因为它具有封装性,所以它应该负责保持这个集合的状态,比如拒绝一些非法的的修改
class PublicationRepository {
  public void save(Publication pub) {
   if (hasSameName(pub)) {
     throw new InvalidPublicationException();
   }
   dao.save(pub);
  }
}



严重同意。repository是与DAO有很大区别的。Dao只是数据库操作接口,但是仓库需要保证领域对象的完整性。
0 请登录后投票
   发表时间:2008-12-03  
terranhao 写道
新手问一下,不用DAO,假如我要条件查询怎么解决?
比如select * from entity as e where e.name=:name and e.id=:id
写在service里面?



放在Service里面也就一句连写
0 请登录后投票
   发表时间:2008-12-03  
serivce{
Dao dao;
...
public List<A> queryA(){
return dao.find("From A as obj Where obj.name='A'");
}
}
0 请登录后投票
   发表时间:2008-12-03   最后修改:2008-12-03
to 楼主:

关于贫血模型的缺点:
引用
“# 将所有的业务放在无状态的service中实际上是一个过程化的设计,它在组织复杂的业务存在天然的劣

势,随着业务的复杂,业务会在service中多个方法间重复。
# 当添加一个新的UI时,很多业务逻辑得重新写。例如,当要提供Web Service的接口时,原先为Web界面

提供的service就很难重用,导致重复的业务逻辑(在贫血模型的分层图中可以看得更清楚),如何保持

业务逻辑一致是很大的挑战。”


这两点我不太赞同,说“业务会在service中多个方法间重复”这可以通过即时的重构解决,如抽取private方法,甚至在“多个service之间重复”时可以抽出包含共用逻辑的类;

第二句话“service就很难重用”我觉得也说不过去,贫血模型中的service是无状态的,可以纯粹看做一段组织好的代码,别的什么地方要用这个逻辑,直接将这段代码inline调用即可,不必担心任何状态相关的问题;总之要在webUI和远程接口之间共用某个service是肯定可以的。

service里的业务逻辑代码会越来越长我赞同,并且正亲身体验中;尤其对于变化很快的某个行业的逻辑;
但是别忘了,上了一定规模的系统,目前还是很少采用领域模型的方式来组织的,原因如楼主描述的“领域模型的缺点”,其实别看小小的几句话,里面涵盖的风险是很大的,特别对于大型系统;或许还因为目前的大型系统一般建立地比较早,而采用了早已成熟的贫血模型,由数据库驱动而来的系统显然贫血模型更加自然,开发人员也好找。

to taowen:

引用
“另外,Repository只应该负责Aggregate Root。对于被Aggregate的对象,应该用Navigation,也就是在

关系之间游走来获取。所以不是所有的查询都必须由Repository来完成”


这么说来repository只用来取root?从概念上讲不错,可是要知道往往一个root下面会有成百上千个node,这么巨大的一坨关系树我们用repository取出来然后在“关系之间游走”...是否有点滑稽?呃...或许我们能想到引入延迟加载,取出来的root下的二级node都用stub来占位;不过这又为污染我们纯洁的领域模型创造了良机...
我想你肯定解决过这类问题,能否多介绍一点?

最后,关于DAO是否该有逻辑,我谈谈我遇到过的情况:
最典型的——一个比较复杂的查询页面,这个页面接收n个参数,比如id,keyword,name,startDate,endDate,dateRangeEnabled。。。。等等等等。。
这n个参数之间还有着这样的三角关系:
当用户输入id时,其余输入参数无效,只按id查询;
如果用户没选中“dateRangeEnabled”,输入的startDate和endDate无效,按其它条件查询;
当用户选中“dateRangeEnabled”时,如果同时也输入了startDate和endDate,那么查询这两个时刻组成的区间;如果只输入了startDate或endDate,就只按>startDate或<endDate查询...
............等等等等........

像这样的查询逻辑,其实就是业务逻辑,并且很难组织到model中,看上去最自然的方式其实也就是写在数据层,由于我们用了某种sql映射框架,这种逻辑一般直接和sql语句待在一起,由该框架提供的一点微弱的脚本编程能力通过传入参数的不同来组装生成不同的查询语句,最后执行。其实跟写在DAO里面是一回事,我们只是觉得利用脚本编程来组织要稍微比在DAO中使用java语言组装sql要省一些代码,其实是一样的,在同一个层次中。

那么,我举的这个例子是否说明DAO不可避免地会有“需要掺杂逻辑”的情况呢?
0 请登录后投票
   发表时间:2008-12-04  
想请教一下...order和orderitem是1对多的强关联, 我要分页列出orderitem...充血模型是如何处理的?
1. 用Repository find(offset, pagesize)出来?
2. order.getOrderItem(offset, pagesize)出来...如果是这种...要么在load出order的时候把所有orderitem全load出来,要么lazy方式...如果是lazy方式...order又去哪里搞到connection呢?

不是太明白..望赐教..谢谢
0 请登录后投票
   发表时间:2008-12-04  

我同意 pf_miles 的回复,
对于楼主的“所谓的”明显的优点和缺点, 我觉得有待商榷。

引用

贫血模型的优点是很明显的:

   1. 被许多程序员所掌握,许多教材采用的是这种模型,对于初学者,这种模型很自然,甚至被很多人认为是java中最正统的模型。
   2. 它非常简单,对于并不复杂的业务(转帐业务),它工作得很好,开发起来非常迅速。它似乎也不需要对领域的充分了解,只要给出要实现功能的每一个步骤,就能实现它。
   3. 事务边界相当清楚,一般来说service的每个方法都可以看成一个事务,因为通常Service的每个方法对应着一个用例。(在这个例子中我使用了facade作为事务边界,后面我要讲这个是多余的)

其缺点为也是很明显的:
   1. 所有的业务都在service中处理,当业越来越复杂时,service会变得越来越庞大,最终难以理解和维护。
   2. 将所有的业务放在无状态的service中实际上是一个过程化的设计,它在组织复杂的业务存在天然的劣势,随着业务的复杂,业务会在service中多个方法间重复。
   3. 当添加一个新的UI时,很多业务逻辑得重新写。例如,当要提供Web Service的接口时,原先为Web界面提供的service就很难重用,导致重复的业务逻辑(在贫血模型的分层图中可以看得更清楚),如何保持业务逻辑一致是很大的挑战。


缺点1:
    我的观点是:
        a) Service层可以而且必须经过良好的组织的以达到清晰、简洁、复用的效果!
        b) 不经过良好组织的Service层,不管用贫血的、还是充血的模型, 都是难以理解和维护的!
缺点2:
    过程化的设计在组织复杂的业务存在天然的劣势? 何以见得? OO到最终的程序不都是在过程化的运行吗?
    随着业务的复杂, 业务会在service中多个方法间重复?? 这又是哪门子的道理? 难道贫血模型中就不能用
封装不能复用了? 还是在java写东西呀!
    业务逻辑的复杂会导致Service的膨胀,但是那是必然的,因为业务逻辑复杂了。 Service可能从一个变成多个,
从一个类中简单的几个方法变成一个类中有十来个方法, 但是这些在rich模型下,一样无法避免,我们只能通过
良好的组织各个类来使它们容易理解和维护!

缺点3:
    当添加新的UI时,业务逻辑得重新写? 为什么?
    我的观点:
    业务逻辑如果需要重写, 那就意味着业务逻辑发生了改变! 只有当业务逻辑发生改变的情况下, 才需要重写!
    实际上, 在贫血模型下, 模型给上层(UI层)的接口不仅仅包括Service层, 还包括贫血模型中的那些类似值对象的POJO, 这已经是在业务逻辑不改变的情况下,上层所能拿到的最完整的模型, 只要逻辑不改变, 上层所要
做的就是对这些POJO的操作和对service层的调用,UI改变根本不需要重写domain层!
    这一点上, 看不到rich模型有什么优势, 甚至贫血模型更有优势,因为提供的模型很完整,而rich模型下,封装
之类可能导致一些底层信息的屏蔽, 这可能会在将来的扩展当中带来麻烦。


再看楼主的例子, 所谓业务逻辑被移走了, 只是在 Account类中封装了 credit/debit方法。
在我看来, 首先, 这是很合理的一种封装, 这种封装不应该成为两种模型之间的根本区别, 贫血模型下一样可以
这样封装,可以在贫血的POJO里面,也可以在service层也可以这样封装,适当而已。
其次, 这个封装的credit/debit方法,并不是真正的业务逻辑, 真正的业务逻辑应该是完整的:
       credit/debit + save data !

我觉得楼主的所谓充血模型,只是 贫血模型 + 一些简单封装 !
如果要做真正的充血模型, 那么在Account的credit/debit方法里面请把 save也塞进去!


下面是楼主写的“充血模型的优点:”
引用

与贫血模型中的TransferServiceImpl相比,最主要的改变在于业务逻辑被移走了,由Account类来实现。对于这样一个简单的例子,领域模型没有太多优势,但是仍然可以看到代码的实现要简单一些。当业务变得复杂之后,领域模型的优势就体现出来了。

优缺点

其优点是:
   1. 领域模型采用OO设计,通过将职责分配到相应的模型对象或Service,可以很好的组织业务逻辑,当业务变得复杂时,领域模型显出巨大的优势。
   2. 当需要多个UI接口时,领域模型可以重用,并且业务逻辑只在领域层中出现,这使得很容易对多个UI接口保持业务逻辑的一致(从领域模型的分层图可以看得更清楚)。

其缺点是:
   1. 对程序员的要求较高,初学者对这种将职责分配到多个协作对象中的方式感到极不适应。
   2. 领域驱动建模要求对领域模型完整而透彻的了解,只给出一个用例的实现步骤是无法得到领域模型的,这需要和领域专家的充分讨论。错误的领域模型对项目的危害非常之大,而实现一个好的领域模型非常困难。
   3. 对于简单的软件,使用领域模型,显得有些杀鸡用牛刀了。
1 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics