关于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点多了。
洗洗睡了