`

Hibernate实体类 == 领域模型 ?

阅读更多
Hibernate实体类==领域模型 ?

http://www.iteye.com/topic/11608



自从Martin Fowler的DDD提出来之后,无数的人就开始非议ORM方式下的持久化实体类,抨击这种方式下的实体类是“贫血”的,缺乏丰富业务语义的。其实他们都犯了一个最基本的逻辑错误 - 偷换概念。

概念是如何被偷换的呢?请注意,领域模型(Domain Model)是一个商业建模范畴的概念,他和软件开发并无一丝一毫的关系,即使一个企业他不开发软件,他也具备他的业务模型,所有的同行业的企业他们的业务模型必定有非常大的共性和内在的规律性,由这个行业内的各个企业的业务模型再向上抽象出来整个行业的业务模型,这个东西即“领域模型”。一个掌握了行业领域模型的软件公司,根本不需要再给人家开发项目了,根本不需要靠软件开发养活自己了,你光给这个行业的企业提供业务咨询已经赚得非常丰厚的利润了。以我现在兼职所在的公司来说,就是这样一家软件公司,在行业内积累了足够的领域模型,成立了一个专门的咨询部门,这个部门下面都是咨询师,他们是不管软件开发的,也不懂软件开发,他们就专门教这个行业的客户,教他们怎么去做自己的业务,他们比客户还精通客户的业务,光是业务咨询已经可以为公司带来很多的收入。

而软件开发呢?一个并没有行业经验积累的软件公司,它开发的软件,基本上完全是需求驱动,而不是领域模型驱动。只有具备了领域模型积累的公司才有资格去谈领域模型驱动软件开发。在由领域模型往某种编程语言如Java上来实现的时候,绝对不会是1:1的对应关系,即使是粗颗粒度的EJB2模型都做不到,更不要说更加强调细颗粒度的POJO模型呢?用面向对象的语言如Java来编写一个领域模型,如果是用EJB2模型,你需要使用最少两个以上的EJB,即一个 Session Bean,处理面向流程的控制逻辑,一个Entity Bean,处理面向持久化的实体逻辑(持久化操作附着在Entity Bean的Home接口上)。如果是更加复杂的领域模型,那么你需要更多的EJB,也许是一个领域模型需要多个Entity Bean和多个Session Bean。现在我们使用基于POJO模型的实现,那么粗颗粒度的EJB还要继续细分:一个Entity Bean要剥离出来至少三个以上的POJO,即一个或者多个实体类,一个或者多个DAO接口类,一个或者多个DAO接口实现类;一个Session Bean要切分为多个业务Bean。

由此我们终于看出来概念是怎样被偷换的了,一个商业概念的抽象领域模型被一个Java持久化实体类替代了。但是我们应该看到,Martin批评的贫血的领域模型并不是Hibernate实体类,Martin指的贫血的领域模型实际上是缺乏丰富业务逻辑概念的领域抽象模型,这和Hibernate实体类完全是风牛马不相及的东西。而Hibernate实体类只是具体编码过程中,为了实现一个领域模型而编写的一组基于POJO的对象中的,完成领域模型某个特征的类。而这个领域模型完整的特征并不应该,也不可能由一个非常粗颗粒度的单类完成,而是由一组互相协作的类完成:即Hibernate的实体类保持领域模型的状态;DAO接口实现类完成领域模型的持久化操作;Spring Bean类完成领域模型的逻辑控制功能。




POJO指的就是非EJB那种重量级,高侵入性的组件模型,关于POJO的定义,你同样可以在Martin Fowler的bliki上面找到。

Spring的Bean是不是POJO? 是的!
Hibernate的entity是不是POJO?是的!
DAO接口是不是POJO?是的!
EJB是不是POJO? 不是的!

我没有看过Martin的DDD,我按照自己的理解, POJO domain models指的就是轻量级的领域模型。何为轻量级? 把领域模型的各个特征,各个属性,各个逻辑都塞到一个class里面叫做轻量级吗?

我认为,Martin批评的贫血的领域模型是指只关注了领域模型持久化特征方面,而忽略了领域模型其他特征方面的模型,这样的模型是贫血的。因为这种模型只关注了模型在技术层面的外在表现,也就是说只关注了数据的存取操作,而忽视了模型蕴含的业务核心价值。

举例来说,我们编一个银行软件,如果你只关注了账户的增删改查,这叫做贫血!而实际上你应该关注的是账户的业务特征,而不是数据特征,你应该关注的是账号开立的业务,账户注销的业务,账号过户的业务等等,这才是领域模型。这种领域模型在一个单纯的技术实现层面来说,对于最简单的业务,你可能只是Account类的增删改查,但是对于复杂的业务来说,他就不单但是一个类,一个表的简单操作了,例如开立账户,你要收手续费,以及考察个人财务状况,那么此时你需要的就是一组协作的类。

Martin提到领域模型,意在强调我们应该关注软件的业务,关注行业知识的内在规律,并且把这种规律建模为领域模型,批评拿到一个软件,脑子里面光想到数据库增删改查的人。这和我们的Hibernate持久化类毫无关系!

我的看法是:一个抽象的领域模型具备多方面的特征,你需要用一组互相协作的类来完成它,每一个或者一组类承担这个领域模型的某个特征。例如某个领域模型,例如上面的账户,你需要一组Hibernate持久化类:包括Account类,User类,Finance类,一组SpringBean类,AccountManager,FinanceManager,一组DAO接口和实现类。由这些POJO的类互相协作来共同完成这个领域模型。如果你仅仅关注Account的增删改查,那就贫血了,而如果你关注了账户的业务规则,并且考虑一组互相协作的类去完成它,就不是贫血的。



问题:
很赞同“商业概念的抽象领域模型”的概念。概念搞清楚固然重要,不过,这些“商业概念的抽象领域模型”在我们的应用开发中处在一个什么层次中呢,如何表现呢?在使用hibernate的系统中,这些“商业概念的抽象领域模型”不是通过hibernate的实体类表现出来的么,如果不是,那么用什么其他表现手段合适呢。

回复:
我上面已经重点强调过了,这种商业概念的抽象领域模型是独立于软件开发体系之外的,你就算不开发软件,你也可以并且需要去抽象你的领域模型。
具体到软件开发活动,你应该由领域模型来驱动你的软件内在规则,由需求驱动你的软件外在交互。
这个领域模型的代码实现我上面也强调过了你需要用一组互相协作的类来完成它,每一个或者一组类承担这个领域模型的某个特征。而Hibernate的实体类只不过是其中的一组类,它承担的职责就是保持领域模型的状态的。



Martin Fowler提出Domain Model的本意就是希望能提升业务领域模型的重用性,也就可知Domain Model是针对业务而抽象产生的模型,此模型和数据源层的持久模型并不一定完全相同,Domain Model中关注的层面更多的是业务方面,Domain Model由Domain Object构成,Domain Object共同协作完成业务逻辑的处理,觉得概念上而言Hibernate的实体类并不等同于Domain Model中的Domain Object,但由于很多应用的业务逻辑几乎是没有,在此时Hibernate的实体类同时也就可以充当Domain Object,觉得这也是为什么两者容易搞混的原因吧!



这实际上是因为术语的不同定义和理解导致的问题。
Martin Fowler提到的Domain Model并不是你这里所说的概念。
你所说的概念是RUP的概念,领域模型是作为业务建模的简化版本来定义的。
想想看,Martin Fowler是把TransactionScript,TableModule,DomainModel三者是放在并列的位置讨论的。用你的观点来解释能解释得通吗?
下面是martin Fowler认为 贫血的DomainModel的问题所在。
The fundamental horror of this anti-pattern is that it's so contrary to the basic idea of object-oriented design; which is to combine data and process together. The anemic domain model is really just a procedural style design, exactly the kind of thing that object bigots like me (and Eric) have been fighting since our early days in Smalltalk. What's worse, many people think that anemic objects are real objects, and thus completely miss the point of what object-oriented design is all about.
数据与行为分离,违反OO封装的基本原理。我想这是最明显不过了。


robbin :

把我的数据和你的行为整合到一起,这就是OO封装的基本原理? 你要搞清楚什么才是一个数据类的内在行为,而不要把本来不是这个数据类的行为误以为是这个数据类的行为。

我可以给你一个准则: 实体类是有状态的类,凡是针对实体类有状态的操作,你可以考虑一下是否是该实体类的行为,而凡是无状态的操作,它基本上就不是该实体类的行为。

还是就上面举的这个例子来看,Order类的addItem方法和removeItem方法是和Order类的某个具体实例紧密绑定的,换句话说就是,这两个方法是有状态的行为,所以他们是Order类的行为,必须放在Order类里面,而其他的所谓updateOrder,saveOrder,findOrders等等统统都不是和具体Order类实例绑定的,即全部都是无状态的操作。这些无状态的操作并不是实体类的行为,他们和实体类的关系只是消息的消费者和消息之间的依赖关系,并不是类内部属性和方法之间的紧密关联关系。。

这些无状态的方法只不过接收实体类作为消息来消费他们而已。我们对比一下Struts Action的
 
execute(HttpServletRequest request, HttpServletResponse response, ActionFormBean formBean, ...);


Action接收request和response,请注意,Struts的action是无状态的操作,而request和response是有状态的类,在这里request,response,formBean都是消息,而Action的execute是这些消息的消费者,你不会认为应该把Action的execute方法放到request对象里面吧?

好吧,那么我们再看看实体类和实体类的DAO接口:
AccountDao: 
updateAccount(Account account); 


比较一下,完全没有区别!这就说明updateAccount这个行为他和Account之间只存在消息的消费者和消息,而不是类方法的关系。

请记住上面这个Struts Action的例子,按照你们的逻辑,execute方法就不应该放在Action里面,而应该整合到request对象里面去。看起来很荒谬吧,其实你们的逻辑推导的结果就是这样。本质上你们又犯了偷换概念的错误,混淆了类的行为和类作为消息的外部消费者。





partech :

首先,我还是建议你去研究一下RUP同MartinFowler关于DomainModel的不同定义。否则大家讨论就是鸡同鸭讲了。
你说的这个原则我可以不可以这样理解?
有状态的操作,指改变实体属性的操作。
无状态操作,不改变实体属性的操作。
没错,你的这种观点是对的。持久化的方法是不能放入DomainObject中。
看看MartinFowler的大作11章的UnitOfWork和18章的Separated Interface。DomainObject实际上只需要知道UnitOfWork的接口。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics