讨论:WCF可序列化的ActiveRecord实现有意义吗?

本文的目的,是探讨WCF可序列化的ActiveRecord实现是否有意义。

ActiveRecord模式简介

ActiveRecord模式是Martin Fowler的企业应用架构模式中提到的一种数据访问模式,如下图:

image

Person类包含lastName, firstName等这些属性,一个Person类的实例,对应数据库的一个Person表或视图的一条记录,每一个属性,对应数据库记录的一个字段;同时,Person类包含CRUD方法,封装了对数据库的操作;Person类同时还可以包含其他和这条记录相关的业务方法。

 

ActiveRecord模式和Repository模式,从外部接口上看起来在很多方面很像,但理念其实有区别:

  1. ActiveRecord和数据库访问紧耦合,它更像是对数据库中的一条记录的包装类;Repository则隔离了具体的数据访问和领域对象的CRUD的耦合,这样一层隔离的存在,使得Repository更灵活,他可以还是像ActiveRecord一样包装一条数据库记录,却不限于数据库,数据也可以保存在任何地方,只要外部接口不变,其内部实现有更灵活性。
  2. ActiveRecord的属性,一般应该完全对应于数据库的字段;而Repository的属性,则没有这样的限制。
  3. 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进行传递。

  1. 简化CRUD和WCF服务接口。相对于Repository只能使用一个或多个非数据库语义的参数来描述查询条件,如果我们可以用一个Criteria对象来表示一个任意的数据库语义的查询条件,我们就只需要一个Find(Criteria criteria)方法,作为所有的查询条件的入口。则无论对于CRUD本身的API还是WCF服务的API,都得到了极大的简化,而描述能力却大大提升。
  2. 简化数据库查询语句的构造和执行。在数据层的数据库查询的执行端。由于我们的Criteria是基于数据库语义的强类型封装,翻译成SQL的成本,相对于Repository只能使用一个或多个非数据库语义的参数来描述的查询条件到SQL的翻译,成本要低得多,语义上也直接得多。

需要注意的问题:

  1. 如何限制客户端的查询能力?由于采用这样一种方案的ActiveRecord使得客户端拥有了最大化的自定义查询能力,我们一定需要有一些机制,可以限制这种能力:在Find(Criteria criteria)的实现端,在执行查询之前,需要根据一定的规则,验证criteria是否允许执行;在客户端的查询对象上,我们也可以有一些限制,比如,只允许某些字段作为查询条件,或者,有些ActiveRecord是只读的,不允许写操作。
  2. 以数据库的思维来构造查询条件。即使在允许的查询条件范围之内,由于不同的查询组合条件潜在地会导致不同语义的数据库查询被执行,不同的语义可能会导致不同的性能差异,因此,在客户端构造查询条件的时候,更大程度上,我们需要以数据库的思维来构造,而不仅仅是以业务的思维。

有人可能要问,客户端代码需要以数据库的思维来写,或者可能影响到数据层的执行效率,这样的设计是合理的吗?我觉得要看情况。

 

首先,ActiveRecord是允许和数据库紧耦合的,从这个角度,ActiveRecord的客户端本身就是允许通过ActiveRecord的外部接口,接触到数据库特定语义的。从这个角度来说,给予客户端一定的构造查询条件的自由度,至少是说得过去的。

 

其次,所谓ActiveRecord的客户端,对于不同的项目来说,对性能的要求可能有很大差异。比如,如果是一个公司内部项目,客户端的并发,整体的数据量也不是很大,则更多关注的可能是灵活性,那么,可以给客户端尽可能多的自由度;而如果是一个开放的WCF服务,或者并发或数据量很大,那么,可能应该尽可能的减少客户端的灵活性,Find(Criteria criteria)这样过于灵活的接口,也可能是不合适。

 

欢迎讨论!

posted @ 2010-08-08 14:37 Teddy's Knowledge Base Views(1672) Comments(8) Edit 收藏

 回复 引用 查看   
#1楼2010-08-08 18:44 | Galactica      
Find(Criteria criteria)
这种东西根本就不提倡,你等于是把业务逻辑又放到客户端了,我先不说设计上的缺陷,我只说商业上的缺陷,这样的模式,就是告诉客户,我们只要给你一份数据库表结构,你就可以随便查询了,那你怎么挣钱?怎么防止盗版?所以,正确的做法,就是要让客户知道,你买我的软件,只要我们公司倒闭了,你就无法继续使用。所以,客户应该持续支付年费,而非一次付清。

 回复 引用 查看   
#2楼[楼主]2010-08-08 19:19 | Teddy's Knowledge Base      
@Galactica
业务逻辑放到客户端,对于大型的Web系统来说,其实基本上是不可避免的,为了降低服务器的压力,尤其是提高scale的能力,一部分业务逻辑在web端其实是很常见的,甚至可以将一部分不太敏感的业务逻辑放在浏览器的客户端js。不过,本文想表达和讨论的,并非业务是否可以放到客户端的问题。我想讨论的是,像Find(Criteria criteria) 暴露给客户端的数据库的语义(我觉得一定的数据库语义不一定就算是业务),你也可以将criteria理解为一个linq语法的查询。这也不表示,criteria是没有限制的任何查询条件,criteria一般不能随便构造,比如简单的new出来,用于构造出criteria的辅助类上是可以做一些约束的。

当然我文中也提到了,像Find(Criteria criteria)这样给予客户端太多灵活性的接口对很多应用场景是不合适的。但是,是不是应该可以有一些应用场景是合适的呢?

 回复 引用 查看   
#3楼2010-08-09 08:19 | 希望的田野      
个人觉得 activerecord 序列化还是不错的 写dto和相关的的model转dto太麻烦了 还不如在model上定义呢。
至于查询条件放到客户端是不是不太灵活 因为有些find可能需要特殊处理 比方有些find需要缓存 有些find是直接查询数据库

 回复 引用 查看   
#4楼2010-08-09 09:01 | 怪怪      
@Teddy's Knowledge Base
@Galactica
有一个问题是很明确的,对于数据驱动应用的场景(很可能是大多数),开发人员干的活本来就是决定在各种条件下暴露什么不暴露什么。否则的话,任何一个真正熟练操作Excel的白领MM我看学会一定难度的SQL去操作更大规模、更复杂的数据都不成问题,要开发人员也就没有什么用了。

所以关键在于,掌握好这个度。灵活性越高,开发人员维护起来就越容易;不跨过界,就不会产生附加的问题。不过其实只要那个白领MM不能决定“暴露什么不暴露什么”,饭碗是一定能保住的。

 回复 引用 查看   
#5楼2010-08-09 09:21 | Galactica      
@Teddy's Knowledge Base

引用Teddy's Knowledge Base:
@Galactica
业务逻辑放到客户端,对于大型的Web系统来说,其实基本上是不可避免的,为了降低服务器的压力,尤其是提高scale的能力,一部分业务逻辑在web端其实是很常见的,甚至可以将一部分不太敏感的业务逻辑放在浏览器的客户端js。不过,本文想表达和讨论的,并非业务是否可以放到客户端的问题。我想讨论的是,像Find(Criteria criteria) 暴露给客户端的数据库的语义(我觉得一定的数据库语义不一定就算是业务),你也可以将criteria理解为一个linq语法的查询。这也不表示,criteria是没有限制的任何查询条件,criteria一般不能随便构造,比如简单的new出来,用于构造出criteria的辅助类上是可以做一些约束的。

当然我文中也提到了,像Find(Criteria criteria)这样给予客户端太多灵活性的接口对很多应用场景是不合适的。但是,是不是应该可以有一些应用场景是合适的呢?


我觉得用在公司内部比较合适,如果给第三方使用的话,还是使用基础服务、组合服务和流程服务的SOA分治模式比较好。


 回复 引用 查看   
#6楼2010-08-09 10:55 | Kain      
这种问题其实都可以折中的,但是个人认为大部分情况还是不建议这样使用,提高业务服务器的性能方式有很多,牺牲安全这个不可取。其实先这种可以用OData的方式来实现还是不错的。
 回复 引用 查看   
#7楼2011-04-02 08:04 | 大石头      
我们的XCode是一个充血模型的Orm,基本上就是你所提到这种模式。
说它会保持数据库连接,那纯属是胡乱猜测!

实际上,实体类有个特性记录着连接名ConnName,需要执行数据库操作的时候,才调用DAL.Create(ConnName)得到一个DAL,它自身根本就不会保存DAL的实例。
不操作数据库的时候,它跟数据库一点连接也没有!

如果做客户端,实际上我们会做点手脚,直接把实体类的那些静态方法给暴露给客户端,这些方法可都是业务方法,而不是大多数人那样暴露数据操作方法。

 回复 引用 查看   
#8楼2011-05-05 22:35 | banana.totolv      
good