Nhibernate + WCF + ASP.NET MVC + NVelocity 對PetShop4.0重構(二)——領域模型
什麼是領域模型?領域模型是對領域內的概念類或現實世界中對象的可視化表示。又稱概念模型、領域對象模型、分析對象模型。它專 注於分析問題領域本身,發掘重要的業務領域概念,並建立業務領域概念之間的關系。
當我們不再對一個新系統進行數據庫提煉時,取而代之的時面向對象的模型提煉。我們必須大刀闊斧地對業務領域進行細分,將一個復 雜的業務領域劃分為多個小的子領域,同時還必須分清重點和次要部分,抓住核心領域概念,實現重點突破。
著名建模專家Eric Evans提出了Domain Driven Design(領域驅動設)。最初層次只分為三層:表現層、業務層和持久層,DDD其實告訴 我們如何讓實現業務層。
領域模型種類
傳統模型分為兩種:實體(Entity)和值對象(Value Object),服務(Service)成為第三種模型元素。
領域驅動設計有兩種常用的模式:貧血模式和充血模式。
貧血模式:只有狀態,沒有行為。
貧血模型
public class OrderInfo
{
public virtual int OrderId { get; set; }
public virtual DateTime Date { get; set; }
public virtual string UserId { get; set; }
public virtual decimal OrderTotal { get; set; }
public virtual IList<LineItemInfo> LineItems { get; set; }
public virtual int? AuthorizationNumber { get; set; }
}
}
Service
public class OrderManager
{
public IOrderDao CurrentDao { get; set; }
public object Save(OrderInfo entity)
{
ProcessCreditCard(entity);
return CurrentDao.Save(entity);
}
private void ProcessCreditCard(OrderInfo entity)
{
Random rnd = new Random();
entity.AuthorizationNumber = (int)(rnd.NextDouble() * int.MaxValue);
}
}
從上面貧血模式的Domain Object可看出,其類代碼中只有屬性,這種Domain Object只是單純的數據載體。雖然它的名字是Domain Object,卻沒有包含任何業務對象的相關方法。這樣,方法都寫在服務層(Service)中,會使得層次更加明顯。但隨著業務方法增多,會使 服務層中的代碼過於臃腫,從而難以維護。
充血模式:既有狀態,又有行為。
充血模型
public class OrderInfo
{
public virtual int OrderId { get; set; }
public virtual DateTime Date { get; set; }
public virtual string UserId { get; set; }
public virtual decimal OrderTotal { get; set; }
public virtual IList<LineItemInfo> LineItems { get; set; }
public virtual int? AuthorizationNumber { get; set; }
public void ProcessCreditCard()
{
Random rnd = new Random();
this.AuthorizationNumber = (int)(rnd.NextDouble() * int.MaxValue);
}
}
從充血模型的代碼中可以看出,其類既有屬性又有方法,服務層(Service)的代碼比較少,相當於門面。方法和屬性的混合編碼方式, 在被多個業務類調用的情況下,代碼的內聚性比較好。假設AManager和BManager都使用到了 OrderInfo,ProcessCreditCard方法只在 OrderInfo中寫了一次就足夠了。這樣,職責性就更加明顯。
這兩種模型都是穩定的,至於用哪種模型,還是需要根據具體需求來斷定。在大多數.NET項目中使用貧血模型要比使用充血模型的情況 多。
領域模型的對應關系一般有三種:一對一,一對多(多對一),多對多。
一對多
public class OrderInfo
{
public virtual int? OrderId { get; set; }
public virtual DateTime Date { get; set; }
public virtual string UserId { get; set; }
public virtual decimal OrderTotal { get; set; }
public virtual IList<LineItemInfo> LineItems { get; set; }
public virtual int? AuthorizationNumber { get; set; }
}
public class LineItemInfo
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual int Quantity { get; set; }
public virtual decimal Price { get; set; }
public virtual string ProductName { get; set; }
public virtual string Image { get; set; }
public virtual string CategoryId { get; set; }
public virtual string ProductId { get; set; }
public virtual OrderInfo Order { get; set; }
}
OrderInfo和LineItemInfo是一對多的關系。在NHibernate的雙向外鍵中,實體之間是循環引用的,所以不能直接序列化。作為每個實 體來說他們都是POJO(脫離框架一樣能使用),在更換ORM框架的情況下一樣能夠使用他們。
“一對多(多對一)”一般用於集合外鍵的ORM描述。“多對多”的關系一般需要一個外鍵關系表。“一對一”的關系一般用於繼承映射 或者用於把一個多字段的表拆分成多個輔助表。
NHibernate的映射配置有兩種方式,一種是在單獨的.hbm.xml文件中配置映射關系,另一種是使用NHibernate.Mapping.Attributes的 方式標注映射關系。後者省略了很多繁瑣的配置文件,但需要應用了NHibernate.Mapping.Attributes程序集。前者有大量的配置文件,但 是不需要引用其他程序集,並且在脫離ORM框架下也能夠使用。所以我個人傾向於前者。
OrderInfo.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="PetShopOrder.Domain" namespace="PetShopOrder.Domain">
<class name="PetShopOrder.Domain.OrderInfo, PetShopOrder.Domain" table="Orders" lazy="true" >
<id name="OrderId" column="OrderId" type="Int32" >
<generator class="native" />
</id>
<property name="Date" type="DateTime">
<column name="OrderDate" not-null="true"></column>
</property>
<property name="UserId" type="String">
<column name="UserId" length="20" not-null="true"></column>
</property>
<property name="AuthorizationNumber" type="Int32">
<column name="AuthorizationNumber" not-null="false"></column>
</property>
<property name="OrderTotal" type="Decimal">
<column name="TotalPrice" precision="10" scale="2" not-null="true"></column>
</property>
<bag name="LineItems" inverse="true" lazy="true" generic="true" cascade="all" table="LineItem">
<key column="OrderID"/>
<one-to-many class="PetShopOrder.Domain.LineItemInfo, PetShopOrder.Domain" />
</bag>
</class>
</hibernate-mapping>
在持久層中每個實體模型都對應了數據庫中的一個表,每個屬性都對應了表中的一個字段,每個實體對象對應了表中的一條記錄。
在服務層中,需要得到的模型對象往往和持久層的實體模型不一致,如某個類中有屬性:數量,單價和金額,但是數據庫中只有數量和 單價。這時候需要建立一種模型——業務模型。然而Linq和匿名類的出現緩解了這一點。在門面層調用服務層返回DTO對象的過程中,通過 Linq查詢實體模型來方式返回DTO。這樣業務模型就能夠被省略(後面的文章會談到這一點)。
最後在設計領域模型中我們需要分清“主次”。當設計進銷存系統,業務類數據就數主要的,如采購,銷售,庫存信息。基礎資料數據 就是次要的,如供應商和客戶信息。當設計客戶關系管理系統時,客戶資料數據則是主要的,圍繞的客戶產生的活動,社交等數據則是輔 助數據。這就產生一個規律:主要數據的變化頻率比較高,輔助數據變化頻率比較低。在PetShop4.0中,主要數據當然是訂單。這樣我們 就需要想方設法去優化系統,以便於更好的處理訂單數據。
出處:http://www.cnblogs.com/GoodHelper/archive/2010/06/18/SpringNet_PetShop4_2.html