3.1 架构模式
首先介绍的一组模式是架构模式,它要解决的问题是驱动领域逻辑访问数据库的方式,此时的选择对于设计影响深远,而且难以重构,因此这个问题需要注意。同样,如何设计领域逻辑也会对这个选择产生重大的影响。
尽管SQL已经在商业软件中广泛应用,但它在使用中还是存在一些缺陷,许多应用程序开发者并不能充分理解SQL,因此,不能很好的构造有效的查询语句和命令。尽管现在有很多技术可以把SQL语句嵌入到程序设计语言中,但它们多少都有点笨拙,可能使用适合程序开发语言的机制来访问数据会更好。DBA也希望能得到访问数据库表的SQL语句,这样他们就能理解怎样才能最好地调整和组织索引。
基于这些原因,把SQL访问从领域逻辑中分离出来,并把它放到独立的类中,实在是明智之举。有一种方法能很好的组织这些类:让它们以数据库中的表结构为基础,这样,每一个数据库表对应一个类,这些类为数据表建立了一个入口。应用程序的其它部分根本不需要了解任何与SQL有关的事情,而且很容易就能找到所有访问数据库的SQL,这样就使专攻数据库的开发者有了一个清晰的目标。
使用入口的方法主要有两种,行数据入口和表数据入口:
最显而易见的是为查询语句返回的每一行产生一个它的实例,这种行数据入口就像是用面向对象的方式来看待数据。
表数据入口与记录集非常匹配,这使得它们成为使用表模块的当然选择。它也是一个组织存储过程的模式。许多设计者都喜欢通过存储过程来完成所有的数据库访问,而不是直接使用SQL语句。在这种情况下,可以考虑把存储过程的集合看成是一个为表定义的表数据入口,可能还有一个内存中的表数据入口来包装对存储过程的调用,这样就可以把存储过程的调用机制封装起来。
如果使用领域模型,还会有更多的选择,当然可以将一个行数据入口或表数据入口与领域模型一起使用,不过,在我看来,这样要么过于间接,要么不够间接。
在简单应用中,领域模型是一种和数据库结构相一致的简单结构对应每个数据库表都有一个领域类,这种领域对象的业务逻辑复杂度适中,在这种情况下,有必要让每个领域对象负责数据库的存取过程,这也就是活动记录,从另一个角度考虑,活动记录就是从行数据入口开始,然后把领域逻辑加入到类中,特别是从多个事务脚本中发现重复代码的时候。
这种情况下,入口增加的间接性提供的价值不大,随着领域逻辑变得更加复杂,它就慢慢转变成一个大的领域模型,简单的活动记录开始不能满足要求了,领域类和表的一对一匹配也开始随着把领域逻辑放入更小的类而失效。关系数据库无法处理继承,因此使用策略模式和其他轻巧的面向对象模式非常困难。
所有这些都迫使你随着领域模型的增大而采用间接的方式。这种情况下,入口可以解决一些问题。但它仍然将数据库方案和领域模型耦合在一起。结果就会有一些入口域到领域对象域的转换,这种转换会使领域对象变得复杂
一种更好的方式是把领域模型和数据库完全独立,可以让间接层完成领域对象和数据库表之间的映射,这个数据映射器处理数据库与领域模型之间的存取操作,并且允许双方都能独立变化。
我不推荐把入口用作领域模型的首选持久化机制,如果领域逻辑非常简单并且类和表十分一致,使用活动记录就足够了,但如果领域逻辑比较复杂,数据映射器才是需要的。
3.2 行为问题
所谓行为问题,就是如何让各种对象从数据库中读取出来以及存到数据库中,乍一看,这似乎不成问题,一个客户对象可以拥有加载和保存方法来进行这项工作。确实,用活动记录这是一个显而易见的路线。
一旦加载的对象越来越多,这就不是一件容易的事,尤其在创建了某些行的同时,还修改了其他行的时候,这是由于在修改引用它们的行之前要获得这些新行的主键,而且这个问题虽然小,但不易解决。
有一种专门解决上述问题的模式就是工作单元,工作单元会跟踪所有从数据库中读取的对象,以及所有以任何形式修改过的对象,它同样负责将更新提交到数据库。应用程序的编程人员将工作交托给工作单元,而不是直接调用明确的保存方法。工作单元排列好对数据的操作顺序,把所有复杂的提交处理放在一起,当数据库的交互动作比较复杂的时候,工作单元是一个必要的模式。
可以这样理解工作单元,它是一个对象,充当数据库映射的控制器,在没有工作单元的情况下,一般都是由领域层充当控制器,决定何时读写数据库。工作单元就是来源于把数据库映射控制器的行为分解到它自己的对象中。
加载对象时,必须小心同一个对象加载两次,否则,在内存中就有两个对象和同一个数据库行对应。为了解决这个问题,可以在标识映射里记录读取的每一行,每次读入数据时,必须到标识映射器里去检查一下是不是已经存在,可以返回一个对它的引用。
如果使用了领域模型,就必须合理安排,使得关联的对象一起加载,然而,如果许多对象都是连接在一起的,则读取任何对象都会从数据库中带出大批的对象。为避免这种低效,必须减少带出来的东西,当然,还需要保持接口以便在以后需要的时候来取,延迟加载的主要思想是拥有一个对象引用的占位符。可以采用几种方法,但它们的共同点都是拥有被修改对象的对象引用,它指向的是一个占位符而不是实际的对象,当且仅当想通过链接访问的时候,才会真的去数据库中读取实际额对象,适当使用延迟加载能使每次数据库调用取得刚好够用的数据。