从DDD中实体和值对象的逆向思考想到的

目前好多搞软件开发的人仍然从数据库编程的思想出发考虑问题。实际在我刚接触编程的时候也是数据库的设计思想,脑子里总是想着数据如何存储的,然后再去设计实现。从DDD的角度,这个是完全错误的思想,而且也违背了OO设计思想。
DDD中的实体和值对象在我刚开始接触的时候,理解了一点,但是总感觉欠缺什么地方没有搞明白,总是考虑持久化的问题,总在思考:既然在实体中包含了值对象,那这个值对象的数据如何存储(也许是数据库思想深入的缘故)。但是随着对DDD的深入,突然有了个逆向思维值对象的思考,不知道是否这样理解妥当。
实体和值对象的思考层面是业务层的内容。在这里抛弃如何持久化的问题。我们只是在设计我们的领域模型,让他更符合业务规则。
但是毕竟在软件实现的时候,持久化是我们要考虑的,虽然思考的时机有可能延后,但是不是可以回避的问题。我这样思考,OO是对象的集合,对象间的作用。数据库是过程化,数据是平行的,没有先后,没有主次,那么如果从数据库的思想出发,如何将这些平行的数据转化为OO中的对象是一个出发点(虽然这个出发点不对,但是是逆向的思考),数据库中的字段对应着对象的属性,此时问题来了,如何将这些字段组装到一个对象中。字段在对象中找到合适归属是个问题,以往我们从数据库结构直接生成POJO的做法就是一种,但是从这个角度讲,这变成了等价转换,中间没有任何的变化,只是表现形式不一样而已。表对应实体,属性对应字段。完全成为了一种数据库表的对象表现形式,对象完全成为了数据库的表中数据的载体。这种方式是反模式,也就是我们常说的贫血模型。从这个角度考虑显然是错误。主要是因为中间少了一个环节,这个环节就是如何把数据库的平行结构转换为对象结构,而不是单纯的拷贝(注意:逆向思考)。也就是转换为实体和值对象的结合,代表我们领域里的真正模型!这个也就是利用DDD分析的一个方面(当然DDD的分析不会是我们这里讨论的从数据库角度考虑的问题,我们这里只是逆向的倒推而已)

如何真的能从数据库的这种结果转换为真正意义上的实体和值对象。那么也就完成了关系数据到业务实体模型的转换。
这样才能真正将关系平行数据(过程式开发)向真正实体模型(面向对象开发)转换。
这个时候再想想,我们在开发中的一部分也就是在做目前的逆向思维的操作,通过数据库表结构,利用eclipse 来生成所谓的贫血实体POJO,这个时候完全失去了对象的意义,POJO完全成为了数据的载体,没有业务模型存在。
好一点的做法(尽管很荒唐)是:在数据库结构出来后,自动生成了POJO后,然后再将一些字段合并成为对象,表示他们应该集中到一个或者多个对象中。但这个时候又要被数据库牵着总,因为我们要考虑他们如何持久化到对应的表中的问题,所以这个时候的分析始终脱离不了数据库的结构。依然约束了你的模型分析。
以上说的在开发中很常见,很多人也都是这么来做的。这个反向思考反映了两个问题:
一:我们使用的是面向对象的语言JAVA,但是面向对象的特性好象在这个开发过程中没有任何体现,那我们为什么要用JAVA,用PB,DELPH也可以吧,开发还快,何必自讨苦吃。
二:尽管我们是想利用JAVA来OO,但是从我们开始数据库结构设计开始,我们都是在设计平行关系。没有层次行,只是平行的。所以无论你怎么想在POJO中体现OO,都会被数据库结构牵着走,因为你的脑海始终有数据库结构的影子,违背了数据库设计的结构,就不能被持久化(虽然有hibernate).这样,你依然不能OO。
综上,这样的思考方式终归应用OO语言没有任何意义,还不如去换其他过程语言实现来得快点(因为好多人都说JAVA复杂,而且大多数是以过程开发的人居多)
那么这个时候我们换一下考虑方式,抛开数据库,从DDD的想法出发,设计出的实体模型,既符合业务,又是OO的,那有人会问,这个时候有实体和值对象的问题,如何持久化呢?大家别忘了,hibernate为我们做了。这个也就是我们为什么要用hibernate的原因,虽然我们使用的实体和值对象是两个对象,实体可以对应数据库中的一张表,但是值对象呢,实际也是同一张表了(这只是一种简单的方式而已),只是我们把值对象平铺一下,把里面的属性提取出来,和实体中的属性平行,一同放到数据库中。这个提取,平铺的过程也就是hibernate为我们做了,那么这个时候考虑下,表结构是依据我们的实体和值对象结构来生成的,所以现在不是我们依赖数据库了,而是数据库的结构是受实体和值对象结构约束的(利用了hibernate的hbm2dll来实现),这个时候,数据库已经被我们抛到九霄云外了。不用理采他。他只用来hibernate数据而已。我们只要用我们的JAVA来做OO的东西就行了。
当然,我们还要熟悉hibernate,你才能更好的理解如何将实体和值对象转换为数据库结构。

以上也是我有感而发的,理解不到位的地方请大家指正

[该贴被lovejdon于2009-09-25 17:57修改过]

LZ说的有理。其实说白了,在采用DDD建模后,形成对象模型以后,这个对象模型往数据库睡,以及从数据库组装成对象模型的过程都很麻烦,这个时候就需要通过缓存来解决了,缓存住DDD中的实体以及聚合的根对象,这样免去了每次都需要重新组装对象模型,并且通过缓存中对象模型进行业务逻辑的操作更加符合现实,更加直接,更加容易理解,并且也更加容易保证不变量约束,这些都OO封装带来的好处,而通过数据库,是没办法实现,数据都是裸体的,没有封装的。

这段时间一直在看Java Persistence With Hibernate,感觉好多项目其实是误用了hibernate,大多数开发人员只知道one-to-many等这些多西,因为都是从外键导出类似这种关系的,正如楼主所说,数据库思维太深了.而更多的项目,都是先建好数据库,然后从myeclipse等ide导出pojo和mapping xml等.

现在我越来越体会到了DDD的重要性,看着一个一个项目的设计代码,叫的都很响亮,名字起的也很牛X,文档写的也很诱惑人,给外界看来好象很有水平,很精通,其实呢,前期叫的咣咣响,等真正实施的时候,不管3721的,直接写吧,没有所谓的分析,没有所谓的设计,以前的文档也都纸上谈兵。做项目好象就是设计数据库,自动生成POJO,写DAO,写SERVICE,设计页面,SSH在项目中的应用也就这样了,SPRING的IOC也就体现在ACTION中注入service,service中注入dao,dao中注入session。什么东西都要注入。不允许new的出现。所有的项目都统一的模式,也是公司所有项目的一个模式。可算提个面向接口编程了,结果被很多人都理解成了,只要是个对象,都必须提供接口,这下可好,什么都是接口,不管值得不值得抽象,只要用到的都必须提供接口,包括util类。----大哥们,没听过接口泛滥吗?无奈。。。spring确实给我们提供了良好的事务管理。不管3721,所有service操作,都把事务拿来,包括只读操作也是如此。。。
service每个操作都只是简单的对数据库操作。而不是针对业务逻辑进行的。有可能在业务上是原子的操作,但是在代码中体现不出来原子,而是在action中调用多个service的不同操作完成一个功能,这样已经失去了事务的作用,在这里事务已经仅仅归为对数据库一次操作的事务,而不是针对业务操作的事务。(当然简单的操作还是可以的)。
都在走极端,完全面向接口,完全IOC,完全事务。

所有的一切既体现不出来oo,也体现不出来业务,唯一应用到业务的地方就是在我们设计数据库的时候,用过程的思想设计出的表结构,是依据业务来做的。然后用JAVA写的代码完全脱离业务本身。只要学过JAVA的,会SSH的,不需要了解太多,都能上手操作。现在这里已经不是所谓的技术密集型行业,而变成了劳动密集型的行业了,这就是我们做项目的方式。
实践固然重要,但是实践的背后蕴藏的理论是更加值得大家思考的,错误的实践只会带来副面影响,没有积极的意义,也许在人们认识到错误的时候,错误的实践可以作为反面教材摆在台面上。
也许这就是目前国内IT的现状,有时候想想也没办法,大环境都如此,我们又能如何呢?适应吧,虽然知道有些是错误的。。但是正确的地方心里明白就好,有可能在开发中你的做法是错误的,但是你也要去做,因为你要适应,但是心里明白,我不能这么做!!这样就可以了。:)

还有个问题就是:hibernate在3。0版本以前提供的是不是不是从DDD的角度来建立持久化机制,而是从数据库的角度来做的对象映射,所以才会有开发工具提供这种反向生成的模式。到了后续版本了,提供的annotation这样的机制,才逐渐走上了DDD领域模型实体对象的持久化机制上?这一点不是很确认,请帮忙解答下!谢谢!

>所以才会有开发工具提供这种反向生成的模式
我不认为这样,Hibernate名称本义就是冬眠 什么东西冬眠呢?当然是对象冬眠嘛,所以Hibernate从一开始就是基于OO的。

而生成工具是一些浅薄的好事之徒根据自己的眼界开发的一些工具,老外的思想不都是上OO这个层次的。