首先,我想說明的這是一篇純意識流的文章,
想到哪裡說到哪裡。有強烈數據結構、流程邏輯控的博友可以略過……
關於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的人受其核心思想影響,