Spring.NET企业架构实践之 Nhibernate + WCF + ASP.NET MVC + NVelocity 对PetShop4.0重构(二)——领域模型
什么是领域模型?领域模型是对领域内的概念类或现实世界中对象的可视化表示。又称概念模型、领域对象模型、分析对象模型。它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。
当我们不再对一个新系统进行数据库提炼时,取而代之的时面向对象的模型提炼。我们必须大刀阔斧地对业务领域进行细分,将一个复杂的业务领域划分为多个小的子领域,同时还必须分清重点和次要部分,抓住核心领域概念,实现重点突破。
著名建模专家Eric Evans提出了Domain Driven Design(领域驱动设)。最初层次只分为三层:表现层、业务层和持久层,DDD其实告诉我们如何让实现业务层。
领域模型种类
传统模型分为两种:实体(Entity)和值对象(Value Object),服务(Service)成为第三种模型元素。
领域驱动设计有两种常用的模式:贫血模式和充血模式。
贫血模式:只有状态,没有行为。
{
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 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 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 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框架下也能够使用。所以我个人倾向于前者。
<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
欢迎转载,但需保留版权。