关于ActiveRecord、领域模型以及iBatis的种种想法

首先,我想说明的这是一篇纯意识流的文章,

想到哪里说到哪里。有强烈数据结构、流程逻辑控的博友可以略过……

 

关于ActiveRecord、领域模型以及iBatis的种种想法

 

最近看了面向领域的种种争论,基于ActiveRecord的设计模式确实能将DAO(Data Access Object)对象、DTO (Data Transfer Object)对象和DMO Service (Domain Model Service Object)很自然的合并到一个继承自ActiveRecordBase的子类中。

 

如 DMO对象 PersonBase { public string Name { get; set; } public  int Age {get; set;} public  string State {get;set;} }

而后将这个Person对象加上[ActiveRecord(Table="Persons")]就成为了一个继承自ActiveRecordBase的DAO对象。

而后将其加上[DataContract]注解,就立马成为了一个可供WCF(Windows Communication Foundation)进行传输的DTO(Data Transfer Object)。到此时,这个PersonBase对象只是一个人的原型。具有一些人的属性,如年龄和姓名还有状态。但是这个时候的他还只是个婴儿,除了能设置它的属性、持久化进数据库外什么也做不了。

然后新建Person扩展自PersonBase扩展两个方法:Cry和 StopCry

Person :  PersonBase, IPersonService

  public PersonBase Cry(PersonBase person == null)

  {

    if(person == null)

    {

      this.State = "Crying";

    }

    {

      person.State = "Crying";

    }

    this.Update(); //此处调用继承自ActiveRecord的Update方法来更新数据库

    return this;

  } 

  public PersonBase StopCry(PersonBase person == null)

  {

    if(person == null)

    {

      this.State = "Normal";

    }

    {

      person.State = "Normal";

    }

    this.Update();

    return this;

  }

}

 

再建立Person的Interface

[ServiceContract]

public interface IPersonService 

{

  [OperationContract]

  PersonBase Cry(PersonBase person == null);

  [OperationContract]

  PersonBase StopCry(PersonBase person == null);

}

此时给这个具有行为能力的Person类具有Cry和StopCry两种动作,此时通过WCF可以将此Person发布为一个Web Service或者是其他形式的WCF服务。

 

此时客户端也可以共享Person这个类提供的服务。

通过WCF的ChannelFactory建立一个Proxy对象,在客户端同时扩展这个对象也为Person,而且这个Person对象也继承自服务端的PersonBase,同样这个客户端的Person类同样实现了IPersonService接口

 

Person :  PersonBase, IPersonService

  public PersonBase Cry(PersonBase person == null)

  {

    PersonBase personback = proxy.Cry(this)

    this.State = personback.State;

    return personback;

  } 

  public PersonBase StopCry(PersonBase person == null)

  {

    PersonBase personback = proxy.Cry(this)

    this.State = personback.State;

    return personback;

  }

}

 

字样就实现了一个Person类从DAO到DTO再到DMO Service的功能转换。

其中的核心原则就是代码重复最小化。增加类的复用性,从而避免一个项目同时维护着Data Access Object、Data Transfer Object、以及一个Rich Domain Model Object。

 

此处的Domain Object是一个含有Domain Model Data和Domain Model Service的富DMO对象。

不再贫血了。而且可以端进行简单的继承和重构。如上面例子中 客户端仍然建立一个扩展自PersonBase这个POCO的Person类,而这个类同样实现了IPersonService接口,同样具有Cry和Stop这两种行为。

 

只是服务端的Person是通过操作类本身,然进行相关的行为。

而客户端的Person是通过WCF提供的proxy代理服务端的Person类的Cry及StopCry行为,并且需要将客户端的Person类本身通过this引用作为参数传递给服务端的Service做相关处理,再将处理结果返回给客户端。

可能听起来比较拗口。但是想明白后还是好理解的。

 

至于服务端的Person类和客户端的Person是两个不同的类,为何能在服务端和客户端进行传递呢?

答案就在PersonBase类。不管是服务端的Person类还是客户端的Person类都继承自公用的基类PersonBase。

而Person作为Service出现在服务端的时候,要求传递的DataContract参数为PersonBase类。根据子类能赋予给父类型引用的原则。理论上是可行的。

 

看到这里,ActiveRecord在面向DMO面向领域的模式中的弊病想必大家看出来了吧。

看客们看到这里也许要批评我了,我说了半天,压根就不是Domain Model的面向领域模型嘛~ 瞎搞了。

 

首先说说我对面向领域的编程模型的理解吧:

我认为的Domain Model 是这样的。比如对于一款进销存软件来说,对于销售人员这个销售领域。销售领域关心的是销售的价格,折扣,销售的数量,营销人员是谁。销售提成多少等等。而对于进货人员来说,它只关心进货的价格,进货的渠道,供货商。而对于财务来说,它只关心这个月的销售的金额、以及销售成本和利润等。

但是在数据库中,可能就只表现为两张表,一张是进货表,一张是销售表。

至于财务锁关心的利润,是通过数据库执行相关的SQL语句来进行统计核算,比如统计这个月的销售总额等等。

由于面向财务领域的数据并不真正存在于一个单独的财务表中。因此如果此时再利用ActiveRecord进行财务的统计的时候,这个Domain Model Object就会产生问题了,它应该Mapping到哪个表呢?

 

由于ActiveRecord是基于NHibernate的二次封装,Nhibernate又源于hibernate。

(PS:我对Castle ActiveRecord没有怎么深入的研究,但是我曾经参与过几个基于hibernate的项目的开发,hibernate对于自定义 Sql语句的Mapping 到 Pojo 很麻烦,甚至说是违背了hibernate的核心思想)

 

与此同时并驾齐驱的轻量级SQL Mapping Object iBatis却在这方面做的很好。

它能将任意的SQL查询语句映射到自定义的Pojo上(.net为Poco)。

 

因此,很适合我上述的进销存软件的领域模型的开发。(通过编写特定的SQL语句,用select sum(price * *discount * quantity) as TotalMoney .....诸如此类的语句,将特定语句查询得到的TotalMoney这个字段映射到比如说 AccountingSum这个类的 public  int TotalMoney { get; set; } 属性上。

甚至可以通过两句sql语句的合并,同时完成某一个特定的领域行为。

如销售行为:第一句,insert into Sales (ItemName,Price,Quantity) values ('香皂', 100, 1);

      第二句,update Stock set Quantity = Quantity - 1 where ItemName = '香皂';

这样的隐式SqlTransition来完成一个特定的领域行为。

 

所以我觉得,问题可能出在了这个上面:

无论是hibernate 还是 ibatis 都太固执了。

 

一个非得要面向 Table。将Table与Object绑定死(至少从默认的情况下,推荐Table Mapping To Object)

而另一个,却是没有绑定什么 Table, 但是,却什么都需要写Select xxx,yyy, * from table where xxxx = yyyy

对于简单的从一张表根据Primary Key检索出一条特定的Record都要写一大堆的SQL语句

灵活性是好,可是有点啰嗦,不能像hibernate(ActiveRecord)那样习惯大于约定。

而ActiveRecord(hibernate)又限制了太多的数据库服务器的功能。

对于大批量数据的归纳统计,SQL语句因为其内部运算优势以及相关的语句优化。它的效率要比用hibernate一条一条的遍历数据本身然后做出统计的效率高得高。

 

如:

decimal totalmoney = 0.00M:

foreach(GoodItem item in GoodItemQueryList)

{

  totalmoney += item.Price * item.Discount * item.Quantiy;

}

SQL语句

select sum(price * discount * quantity) from GoodItem where ... 

 

这两种实现方式虽然都能实现相同得功能,但是我像连新手也能看得出在性能优势上孰优熟略

首先从SqlClient与SqlServer的数据量上来看,明显第一种会在数据库客户端和服务器产生大量的数据交流

 

因此,在此处我像表明的是。

ibatis很好,hibernate不错。

但是如果能将两者CrossFire起来,那就火力相当十足啦。(此时我想到了我的上海Volks Wagen CrossPolo,很小很强大!)

 

ActiveRecord(Hibernate)把SQL Server服务器的功能只用了一般,或者说,大多数用Hibernate的人受其核心思想影响,不自觉的利用了其少部分功能。

而iBatis虽然能淋漓尽致的将Sql Server的威力发挥出来,但是代价就是需要声明的东西太多。

对于大的团队来说,可能有专人负责SQL语句的撰写和优化。

而对于像我这样的独立开发者来说,这无疑是一种磨难,一种很痛苦的开发体验。

(试想下:你开发周期的半数时间,都在不停的敲打select from where insert into values update set where delete where,而把更多的本应应用于领域开发的时间荒废了。)

 

what's more,这样对于敏捷型开发很不好。

what's more and more , 根据hibernate思想写出来的Poco对象又是跟 数据库  Table有关的一一对应的Mapping关系

也就是说,用了ActiveRecord以后,你所写出来的类,基本只属于DAO而无法像我上文所说的

让它更成为Data Transfer Object , Domain Model Object。还有,你浪费了客户花了上万元买的 MS SQL Server 或者是Oracle服务器,因为你没有让他们发挥全部的功能,你让他们偷懒了!

 

希望在我写完此篇日志后,能有高人写一套 ActiveRecord + iBatis 的 Combo DAO (Data Access Object ) Model框架,我想我会第一个,很乐意的去用它,哪它存在N多的Bugs,但我想,应该是一件很伟大的作品!

 

Time flies , 一晃凌晨2点多了。

洗洗睡了

posted on 2010-11-30 02:14  gongji  阅读(5100)  评论(23编辑  收藏  举报

导航