玩O/R Mapping的体验,及由它想到的,胡思乱想,思维跳跃
O/R Mapping的作用是什么?接触了很久,最早的时候,是冲着这玩意是一个较新的概念,觉得玩起来与众不同,有点酷酷的感觉。
一开始,对O/R Mapping的了解极为肤浅,一点也弄不清楚O/R Mapping究竟有什么现实的意义,虽然我信奉“一切皆对象”,但也知道,信奉归信奉,局限人类科学发展的与个人技术水平的局限,以及现实的性能需要等等,这是不现实的。
所以,O/R Mapping的真正含义,我还是没能弄清,一开始只是简单地认为,O/R Mapping的核心就是能够包装一下CURD,其它的就没有什么了。
直到对O/R Mapping各类组件开始大量的使用的时候,各类O/R Mapping的实现方式不尽一致,每一种组件都有自己独特的地方,也有自己不足的地方,于是开始了对一些开源的或是可以弄到源码的组件进行了源码分析,随着分析的加入,越来越迷茫,实现了如此多的特性,就是为了面向对象开发,大量地牺牲了性能,这是否值得,而且,很多组件,如Hibernate、XPO.NET等还放弃了数据领域专用的DSL---SQL,使得查询方面的实现复杂度大大增加,如Nhibernate不得不提供HQL来满足需要,但比起SQL这种长久以来久经考验的DSL来说,还确实是稚嫩了一些,许多过于复杂的查询,不得不牺牲巨大的性能来实现,造成了得不偿失的牺牲,这也许也是对象理论的不成熟与关系理论浓厚的数学背景造成的巨大冲击。
微软的Comega,则一个数据性语言,严格地说,它本身与O/R Mapping没有什么关系,但它提供了内嵌SQL的语言支持,的确让人耳目一新,从数据操作的角度来说,给人无疑有一种这是一个革命性的举措。
不过,也正如熊节对于各种新技术所言“古已有之”,确实是这样,就以数据为中心的语言来说,最早的foxbase,以至于后来的foxpro,还有现在还在市场上具有活跃的PB,都是以数据为中心的开发工具,准确地说,它们也许,并不能像纯正的理论派们那样所期望地提供“一切皆对象”的持性,但它们兴旺,很值得人深思。
我一直不清楚的是,微软当初为什么会决定不会对foxpro提供后续的开发与支持,也许是foxpro在.NET的世界之中,是一个异类,它的轻易使用,可能会掩盖C#与VB.NET的光芒,这样说,也许有许多人要反对,认为C#与VB.NET才是真正好语言,才是真正的100% Pure的.NET血统。
其实,我们应该考虑的是:我们究竟需要的是什么?对于MIS系统的开发者来说,语言本身的无论如何强大,都只能保证你可能实现更多的功能,但并不保证你一定能够实现那些功能。像C与C++这类语言,更注重的是如何自下而上地对系统进行精细控制,尽量深入到系统细节内部,而像JAVA、C#、VB.NET则正好相反,追求的是以面向框架或组件式的开发,同时要满足各种领域的需要,而Foxpro、PB这类,则属于领域性的专属语言,它们的专长在于对信息系统的快速构建。
因此,我们为什么需要C与C++?因为它在低层控制上的能力是最强,而且在底层上也是最方便的。我们为什么需要C#与VB.NET?因为它可以让我们更方便地实现自己所需要的应用性功能,可以使用许多现成的轮子或构建自己所需要的轮子。我们为什么需要VF或PB,因为它可以快速地构建我们需要的产品。
从实际出发,针对不同的应用级别需要,应该选择不同的工具来实现。
C#是一个专门为.NET框架设计的语言,它与.NET平台的关系,非常类似于JAVA与JAVA平台的关系,我甚至怀疑过,微软是否曾经打算把直接把C#名字直接叫作".NET",后来才因为商业与市场上的策略,以及现实的压力需要,不得不把这门语言叫做C#,因为只有这样,才能真正实现微软大一统的梦想(对于这个,我们没有必要指责,谁不想发展到这一步呢?)。体现出C#与其它语言的平等性,但实际上,微软对C#不遗余力的推行,这个天平实际上是有了一点倾斜的。
VB是肯定肯定会在.NET上出现的,这是毫无争议的,因为Basic本来就是融入着盖茨本人的梦想,对于VB,是我接触最早的一门语言,也是我个人认为一门非常优秀的语言,VB本身是非编译性的,这个大家都知道。它本身融入的动态特性,在.NET上实现时,可以说是被抹煞了。虽然VB.NET非常优秀,但就选择而言,如果VB6与VB.NET同时在我的面对,我肯定选择VB6,当初VB6带给了我们多少梦想啊。可惜的是,VB6毕竟是要发展的,一个小孩子总要长大,长大后,得到了些许多,但这个过程中,也需要失去许多。不过,VB.NET现在确实开始成熟了,面向对象特性的加入是VB.NET这门语言一个标志性的进步。
C++\CLI,这可能将是2006年最值得期待的语言,C++上天入地,呼风唤雨的本事一向是被众人所称道的。Managed C++丑恶的语法,曾经一度伤害了C++ Fans的心,有不少到.NET平台下度假的Fans们,都选择了C#来做为自己的居处,C#虽然也提供了unsafe,可以使用指针,可以这样,可以那样,但严格来说,这一切不是C#之道,不是C#真正的路。这个目标,与不少的C++ Fans心中的理想是截然不同的。所幸的是,优美的C++\CLI出来了,看着C++\CLI的语法,我觉得,这是一切是一种艺术性的结合,结合的难度是可以猜想的,很难想象出,微软短短的时间之内居然有这种能力,将托管与非托管之间的桥梁如此迅速地搭建了起来。
在.NET中,丢弃了Foxpro,这本身也有Foxpro本身许多的不是之处,不仅仅是因为FoxPro本身的数据库容易损坏等问题,最主要原因是它本身跟微软的商业策略有所冲突。看看微软的产品家庭,Access与Sql Server,是两个非常流行,却又令人觉得奇怪的产品,这两个产品都在极力避免着相互间的冲突,在Office2000的时代,曾经出现过使用Access2000操纵Sql Server2000,居然某些时候,还比Sql Server2000中的企业管理器好用,这不得不说是一个郁闷的事情,许多开发者,当时都掌握了利用Access来管理Sql Server2000的技巧,因为在当时,你给所有的机器都装一个Sql Server的企业管理器是不现实的,但是要是装一个Office2000,这却是毫无困难的。这使得某些时候Sql Server与Access在市场上产生了一定的冲突。
在这种情况之下,Foxpro处于非常尴尬的境地-----它的技术实现决定了它不能成为中型数据库,但如果定位成小型数据库,它本身的可编程特性比Access更强,势必抢夺Access的市场份额。微软在处理Office这个产品上,的确花了不少心思,一方面要有强大的功能,另一方面又不能压着其它产品,Access本身也是支持可编程的,而且编程语言就是用的VBA,与VB6来说几乎没有什么区别,设计界面也十分容易使用,而且也是一个轻量级的工具,但处于Office套件的考虑,还不能将Access的开发处理为直接编译成为单独可执行的exe。除Access产品外,我们在InfoPath中也可以看到类似的迹象,基于InfoPath制作表单可以说是一件轻而易举的事情,但如此轻便的操作方案为什么不用到Asp.net的设计中呢?另外,再保守一点,即使让InfoPath可以直接生成可发布的网页表单,也是件更好的事情啊。(哈,要是这样做的话,怎么卖Biztalk呢?)
这涉及到了微软整体的商业策略,有人抱怨说,微软的东西每一件都看起来不贵,但你买了其中一样后,会发现,要实现一个完整的方案,还需要另一个产品,另一产品将会再牵扯到其它产品.....以至于整体上的花销非常之大。
当产品之间能够成为循环链条的时候,链条以外的产品就会成为鸡肋,当用户的便利性体验影响到商业利益时,是允许牺牲用户的体验的。(微软的产品虽然是世界上最贴近用户的,但这也是出于商业利益的驱使。)
Foxpro的功能虽然很弱,但不可否认的是:曾经有一个时期是它的天下,虽然原来的Foxpro不能满足现在日益增长的需要,但它仍然是可以发展、成长的。
在拥有众有拥护者的情况下,微软还是把Foxpro踢开了,我不知道微软是如何下这种狠心的,这个决策使得一个有望全面性压制PB的产品从人们的视线中开始淡出。
以数据为中心的开发工具,目前只剩下了PB一家,PB专业的精神可谓是令人赞叹,不禁使我想起肯德基的口号:“我们只做鸡”。在微软的网站上,我们可以发现,背地里,微软仍旧在发布着Foxpro的新版本(最新是9)。
一般的信息系统或销售业务系统(国内80%以上的公司都在做这些),无论采用JAVA还是C#、C++还是C,都是砍树用菜刀。力气无论怎么花在刀刃上,再叫究多少技巧都始终无法达到满意的效果,对于这些领域来说,理论上采用PB一类的工具,是可以实现最大化成本的效果。
PB是一个号称4GL的开发工具,至于什么是4GL,我想,有点清醒头脑的人都可以认识到,目前这是不现实的,真正的4GL可能还有很长很长的一段路要走,现在光是MDA就已经把人折腾得够呛,"MDA is die"的言论冲充着我们的周围,新来的产生式编程,其实也是一种退化,如阿飞跟我说的:“结构已死”,让我唏嘘不已。
产生式编程,如果细心一些的人就会发现,这与当初汇编中的宏,就几乎是一个模子中出来的东西,产生式的结果,带来的更高的复杂性,更复杂的思维特性。就如.NET平台上的G#一样,作者说,他是用CodeSmith时,才一下子顿悟到,这样做可以大大地提高编程效率。产生式编程,其实本身也不是一个非常新鲜的东西,在用PHP、ASP.NET做网站时,不是也在有意识的拼凑着html与javascript代码往浏览器里面灌,促使浏览器运行这些代码,从而简化开发过程吗? 只是,产生式编程从一个更高的角度上提高了这一点,使得其成为了一个概念。
说来惭愧,这段时间一直就是想实现一个自己的O/R Mapping组件,一直在为此努力着,最初的想法是:成不成功不要紧,在这个过程中,我肯定可以学到许多有意义的东西。这段时间一直在研究Comega的源码,我想与Comega那样,开发一个数据层专用的语言,该语言应该与C#差不多,但又可以支持SQL语句,并且,能够支持自动更新等等,数据库结构变化后,更新中间组件,不会影响到数据层的功能实现,也就是与数据库的紧密结合。
但Sql Server2005中的存储过程,让我大开了眼界,原来用C#也可以写,让我开始怀疑自己努力的价值....终于,明白了,O/R Mapping不是解决数据库访问用的,也不是银弹。它不是什么方案的解决之道,而应该说是一种妥协之道。
这其中激烈的明悟来源于朋友timill的一句话:“我只关心怎么容易实现我的东西,理论上的东西我不太感兴趣”,中国的理论已经够了,差的是应用的能力,理论与实践,是发展的关键,再好的理论,如果不能用于实践,是毫无意义的。
那么,O/R Mapping的意义究竟何在?Ado.net 中的DataSet又是一个什么玩意呢?
DataSet是一个基于表模式的宝贝,所有的描述来自对象的外部的操作,本质上它是一个容器,里面可以容纳DataTable,而DataTable之下又可以容纳DataRow,.NET下的DataRow最小的单位了,可以说,它是一个数组,这给DataTable带来了巨大的灵活性。微软当初选择这种设计,无疑是明智的,因为对于表模式为中心的映射来说,添加或删除一个字段,对DataRow本身并不会带会巨大的影响,它只会影响到DataTable上,由于对外的访问是主要基于DataTable进行,使得在数据库发生改变的时候,加入新的列不易影响到原来的开发,并且,可以在行级的程度上确定数据访问的正确式,减少雪崩隐患。
面向对象的好处是提供了封装,对象提供了多态,继承等特性,可以对现实的世界进行映射,可以说,更适合于人类的思维方式的就是对象方式。在封装上有许多种技巧,早期的技巧都是如何分类上,现代流行的技巧转移到了如何设计类间的关系上了,当然如何分类还是OO的基本功,确定类的职责也是OOD的核心要素之一。
在DataSet中,包装的结构是灵活的纯数据对象,它与ActiveObject是完全不同的,这也许在信奉Martin的人的眼里不够DDD,但对于.NET平台下来说,这是一个优势的方案。
顺便提一下:有人认为ActiveObject不是一个好东西,是错误的设计,也有人认为,ActiveObject才是真正的DDD式开发,才是真正的对象。这个实际上取决于观察的角度。关键之处是确定开发的软件中,数据访问模式的采用,是否是AcitveObject的区别在于:是采用通过一个管理对象来管理所有的数据还是不允许专门的数据对象,只允许包含了操作的对象,这个决策来自于分析阶段,无所谓对错之分。
对于.NET来说, 1.0与1.1版本并不是一个成熟的版本,有许多.NET的特性化的东西并没有体现出来,当然这其中也包含了许多.NET上的梦想中的功能,这毕竟需要一个成熟阶段,就表模式的DataSet来说,是力图实现关系模式的映射,这是最保守也是保险的方案。有人说Dataset是很象微软以前提过的内存数据库,确实是这样,就纯对象而言,对象的理论是不可靠的,尽管对象应用,成功之处非常之多。但需要注意的是,这些成功是建立在数据封装的理念之下的,而并非是对象理论自身的广泛应用,对象理论更多的是一种思考的方式,而非一种实现的方式。是否对象化思维,全取决开发者本身,利用现代的工具,不少不懂对象理论的人,仍然可以开发出可用的软件,虽然该软件的架构并不怎么样,扩展性与维护性也不一定好,但就是能够做出来,这其中的玄机在何处呢?
对象理论本身并不完善,所以造成了不可能有完美支持对象的语言或开发工具出现,现在兴起的函数式编程,就是力图解决一些现代3G语言很不易解决的对象描述问题。但从另一个角度来说,函数式的编程可以不管是否对象,所以从低层来实现来说,是比较理想的,当然也比较麻烦的。
DataSet是属于比较保守的,不知微软是处于什么样的考虑,.在自己添加了一个DataTable后,NET中并没有提供一个让这个新添加表直接持久化到数据库中产生一张物理表的能力。后来仔细思考了一下,认为.NET中这样设计,也是有DataSet本身的原因在内,DataSet中xml描述的数据类型是string、decimal这种样子的,因为考虑到了与前端程序的紧密结合,那么在数据类型的翻译的时候,由于就关系数据类型与经典的编程中的数据类型相比,关系型的数据是更为丰富的,是从关系型的数据类型中直接映射过来的,这个映射是单向的,反向映射将会产生许多令人不舒服的实现。
同样的,著名的开源项目Gentle.NET也是这样,它只提供了一种对已有的数据库表的映射机制,而没有提供了由程序生成到数据库中的能力。
然而,Nhibernate与XPO就采用了一种妥协性的策略来进行数据类型转换,比如GUID之类的字段,在对于不支持该类型的数据库上,统统映射成为string,因此,这些组件实际上也失去了反向映射的能力。
这也造成了一个巨大的问题:性能。数据类型转化的开销是比较大的,换而言之,现在的标准的、号称支持异种数据库的O/R Mapping工具没有一个是真正实用的,使用它们,就必然要有所妥协,无论是开发灵活性还是性能。
"持久化”三个字已经明确指出了对数据库的态度---应该由以前数据关系考虑为中心转换到现代更自然的业务逻辑关系考虑为中心,数据库只是作为数据保存的一个容器,这本身与数据库产品的思想就有着尖锐的矛盾。
程序本身很难解决数据类型与现实业务逻辑的冲突,尽管可以通过自定义数据类类型来解决绝大多数问题,但这些复杂的数据关系,一般是属于难以持久化生存的。
纯的数据类型的实现,如非类型的DataSet的包装实际上可算是一个ValueObject,这种包装不应该用类来声明,应该采用Struct,但DataSet,有一个特殊的地方,就在于可类型化,该类型化是基于对数据绑定的考虑,数据绑定虽然造成了一定的不可控特性,但无疑的一点是,数据绑定确实大大地提高了工作效率,让软件开发不会在多列多行数据显示的泥沼里挣扎半天。
基于DataSet的设计,采用类要描述是最好的方案,因为一个结构体,是属于形态比较恒定的,对于业务与设计捆绑得非常紧密的软件开发来说,需要一个灵活性的表格,可以大大减轻开发负担,提高开发效率。
个人观点认为,从广义上而言,DataSet的方案也算是一种O/R的形式,只是它在映射成为OO方面还很幼稚,但它完整的关系映射却是绝对不忽略的存在。整个DataSet方案优势的地方在于:DataSet作为容器,支持XML数据描述,这为Web Service方案中的数据传输起到了绝大的简化作用,DataTable做为可灵活定义的结构,支持从外部定义与继承后的内部构造,可以满足灵活的业务需要,DataRow只是数组,没有说的。
如果是采用实体类的方式,在软件中动态创建一个新列,这将是一个最大的恶梦,当然,这并不是不可实现,只是在实现上将要花费不少的周章。比如,你需要继承这个实体类,然后在新类中增加一个新的字段和对应的属性,然后将外问的访问改为基于子类的访问,这样做听起来不错,但问题在于:需要进行重新编译。有时候,这会成为一个不灵活的方案。
未完....