本文的目的,是探讨WCF可序列化的ActiveRecord实现是否有意义。
ActiveRecord模式简介
ActiveRecord模式是Martin Fowler的企业应用架构模式中提到的一种数据访问模式,如下图:
Person类包含lastName, firstName等这些属性,一个Person类的实例,对应数据库的一个Person表或视图的一条记录,每一个属性,对应数据库记录的一个字段;同时,Person类包含CRUD方法,封装了对数据库的操作;Person类同时还可以包含其他和这条记录相关的业务方法。
ActiveRecord模式和Repository模式,从外部接口上看起来在很多方面很像,但理念其实有区别:
- ActiveRecord和数据库访问紧耦合,它更像是对数据库中的一条记录的包装类;Repository则隔离了具体的数据访问和领域对象的CRUD的耦合,这样一层隔离的存在,使得Repository更灵活,他可以还是像ActiveRecord一样包装一条数据库记录,却不限于数据库,数据也可以保存在任何地方,只要外部接口不变,其内部实现有更灵活性。
- ActiveRecord的属性,一般应该完全对应于数据库的字段;而Repository的属性,则没有这样的限制。
- ActiveRecord对数据库访问的紧耦合,也不完全是缺点,紧耦合的好处是,ActiveRecord上的方法的参数,也可以是和数据库紧耦合的,例如:它的CRUD方法的参数,可以包含数据库特定的信息,如字段信息,或者数据库特有的查询条件,面向数据库的查询条件,从数据库的视角当然更直观;而Repository在外部接口上,原则上是不能有任何数据库特定的信息的,如果最终还是要查数据库,还需要额外把非数据库特定的查询条件转换为数据库特定的查询条件,对于参数复杂的查询,处理查询条件的成本要高得多。
什么是WCF可序列化的ActiveRecord?
这里的“WCF可序列化”指的是,一个类可以被支持WCF DataContract序列化的类进行序列化。这里,“WCF可序列化的ActiveRecord”指的是,不仅仅ActiveRecord可序列化,还包括服务于ActiveRecord的查询条件也可以被序列化。ActiveRecord及其查询条件可以被WCF序列化就意味着,他们可以通过WCF服务进行远程通信,换句话说,他们可以作为DTO对象。
在有关ActiveRecord的讨论中,经常会被提到,ActiveRecord不能用作DTO,因为其内部包装着数据库访问,尤其不能跨进程边界传递ActiveRecord实例。这种说法当然没错,不过他有一个前提,就是如果ActiveRecord实例连接着数据库,通过ActiveRecord上的CRUD可以直接访问数据库的话。换句话说,如果可以解除ActiveRecord实例对数据库的连接(例如:可以提供Attach(db)/Detach()方法将一个ActiveRecord实例绑定或解绑具体的数据库连接),那么,把它作为DTO我觉得未尝不可。
让数据库查询条件作为DTO会不会有问题呢?在传统的程序开发中,一般我们说传递“数据库查询条件”往往指的是传递SQL片段,虽然传递SQL使得查询条件非常灵活,却毫无疑问是不安全的。但是,如果是对SQL语义的查询条件进行强类型的包装,则可以做到没有这样的安全隐患,却又可以保留查询条件灵活的特性。
如果从安全性的角度,能够接受ActiveRecord及包含数据库查询语义的强类型的ActiveRecord的查询条件作为WCF服务的DTO进行传递,那么,我们就可以再来谈谈由此带来的好处了。以下,假设我们可以用一个强类型的Criteria对象来表示一个任意的数据库语义的查询条件,并且它可序列化,可以作为DTO进行传递。
- 简化CRUD和WCF服务接口。相对于Repository只能使用一个或多个非数据库语义的参数来描述查询条件,如果我们可以用一个Criteria对象来表示一个任意的数据库语义的查询条件,我们就只需要一个Find(Criteria criteria)方法,作为所有的查询条件的入口。则无论对于CRUD本身的API还是WCF服务的API,都得到了极大的简化,而描述能力却大大提升。
- 简化数据库查询语句的构造和执行。在数据层的数据库查询的执行端。由于我们的Criteria是基于数据库语义的强类型封装,翻译成SQL的成本,相对于Repository只能使用一个或多个非数据库语义的参数来描述的查询条件到SQL的翻译,成本要低得多,语义上也直接得多。
需要注意的问题:
- 如何限制客户端的查询能力?由于采用这样一种方案的ActiveRecord使得客户端拥有了最大化的自定义查询能力,我们一定需要有一些机制,可以限制这种能力:在Find(Criteria criteria)的实现端,在执行查询之前,需要根据一定的规则,验证criteria是否允许执行;在客户端的查询对象上,我们也可以有一些限制,比如,只允许某些字段作为查询条件,或者,有些ActiveRecord是只读的,不允许写操作。
- 以数据库的思维来构造查询条件。即使在允许的查询条件范围之内,由于不同的查询组合条件潜在地会导致不同语义的数据库查询被执行,不同的语义可能会导致不同的性能差异,因此,在客户端构造查询条件的时候,更大程度上,我们需要以数据库的思维来构造,而不仅仅是以业务的思维。
有人可能要问,客户端代码需要以数据库的思维来写,或者可能影响到数据层的执行效率,这样的设计是合理的吗?我觉得要看情况。
首先,ActiveRecord是允许和数据库紧耦合的,从这个角度,ActiveRecord的客户端本身就是允许通过ActiveRecord的外部接口,接触到数据库特定语义的。从这个角度来说,给予客户端一定的构造查询条件的自由度,至少是说得过去的。
其次,所谓ActiveRecord的客户端,对于不同的项目来说,对性能的要求可能有很大差异。比如,如果是一个公司内部项目,客户端的并发,整体的数据量也不是很大,则更多关注的可能是灵活性,那么,可以给客户端尽可能多的自由度;而如果是一个开放的WCF服务,或者并发或数据量很大,那么,可能应该尽可能的减少客户端的灵活性,Find(Criteria criteria)这样过于灵活的接口,也可能是不合适。
欢迎讨论!