總結一下領域模型的驗證(附代碼下載)

作者: xuefly  來源: 博客園  發布時間: 2011-05-29 13:45  閱讀: 2269 次  推薦: 0   原文鏈接   [收藏]  

 

  一:什么是領域模型(Domain Model)

         1,Entities

         2,Value Objects

         3,Relations

  二:只談驗證(Validation)——三種常見的做法

         1,Constructor/Method based Validation

         2,Validate() Method

         3,Validation Services

         4,Validation Configuration

  一:什么是領域模型(Domain Model)

  我們可以在概念層次認為Domain Model就是在大的領域邊界中的,可以用基于離散的思想來限定出的,承載“數據”和“關系”的小邊界(個人給的定義,僅供參考)。定義中蘊含著這樣一層意思:所謂模型乃是我們限定出的用于解決問題的承載著“數據”與“關系”的“問題邊界”,也就是Model不一定跟現實中的真實物理對象一一對應,雖然大都一一對應。領域大邊界由Model小邊界來明確,Model小邊界需要由領域大邊界來給限定出問題的范圍(因為宇宙是無窮的開放的,除了那個終極規律外的任何規律都是有適用范圍的,不限定出范圍就會寸步難行,無法認識任何問題了)。

  領域模型承載的數據可以分為兩個類別:Entities和Value Objects。Model中的數據依照一定“規則”模擬出一個有機的問題模型,這個“規則”約等于Relations。

  1, Entity是這樣的數據,在它的生命周期中需要一個標識(Identity)。在Domain Model中(注意這里的Model是在一個限定的領域中的)如果某條數據脫離了這個標識就沒有了意義的話,那么這樣的數據就是該Domain Model的Entities類別的數據了。比如在cnblogs的系統中,每一個BlogSite都有一個BlogTitle,但是這個BlogTitle是可以修改的,當我修改了我的BlogSite的BlogTile后我卻依然能夠通過“www.cnblogs.com/xuefly”訪問到,這里可以說“www.cnblogs.com/xuefly”就是我的blog的標識了。如果沒有這個標識存在的話,那么BlogTitle在空間中的存在也就沒有了意義(當BlogTitle與BlogSite脫離后BlogTitle就變成了“字符串”,而在Blog領域中“字符串”是沒有意義的)。像BlogTitle這樣的數據就是屬于BlogSite模型的的Entitys類別的數據,我們把這樣的數據交給BlogSite來驗證的話是合理的。比如當我們修改自己的BlogSite的BlogTile中包含敏感的非法字符時或者長度超過了一定限度時,修改就不會保存成功,這里的驗證就該是由BlogSite本身來完成的。

  2, Value Objects是這樣的數據:它的存在不需要標識。存在就是要有意義,還是在cnblogs的系統中,每個BlogSite都會有博主(BlogOwner),因為我們的BlogSite是單用戶的所以在BlogSite中應該會有一個BlogUser:Person類型的屬性,我們假設這個屬性被命名為BlogOwner。

  BlogOwner是一個BlogUser類型的對象,BlogUser類型對象的存在不依賴于BlogSite的存在,也就是即使沒有BlogSite的話這個被指向了BlogSite.BlogOwner的BlogUser類型的對象依然有存在的意義(是否可以這樣理解,BlogUser是一個Person,而Person在Blog領域中是具有意義的)。這里的BlogOwner就可以歸為BlogSite的Value Object類別的數據了。我們看到BlogSite有一個ID標識,這個ID標識是提供給BlogTitle這樣的BlogSite的Entitys類別的數據的,并不是提供給BlogOwner的。再進一步說明就是:在一個BlogSite類型的對象blogSite1中,blogSite1的BlogOwner屬性值原來指向user1,而現在我們把blogSite1.BlogOwner指向null,這時user1失去了與blogSite1的聯系,然而user1卻在整個Blog領域中依舊具有實際的意義,因為在Blog領域中必須有用戶這個概念(BlogUser Class),user1是一個用戶,user1在blog領域中依舊有意義。而BlogTitle就不同了(blogSite1.BlogTitle = blogTitle1),如果我們把blogSite1.BlogTitle指向null的話,BlogTitle1同樣與blogSite1脫離了關系,這個時候blogTitle1由“博客標題”變成了“字符串”,blogTitle1是個字符串,它在Blog領域中沒有意義。BlogOwner屬于BlogSite的Value Objects類別的數據,BlogOwner的數據應交由BlogUser模型來驗證,不應由BlogSite模型來驗證。與BlogSite一樣BlogUser.Name,BlogUser.LoginID,BlogUser.Password屬于BlogUser模型的Entitys類別的數據。

  3,關系(Relations)

  ……

  二,只談驗證(Domain Model Validation)

  業務規則要求我們的Domain Model必須滿足某些約束,比如BlogSite的BlogTitle的長度不能大于等于255個字符等,這就是業務規則。如果說對BlogTitle的長度進行約束貌似還有點不怎么說的通的話,那么在Blog領域中業務規則要求BlogUser類型的對象的Age屬性不能小于等于零就是無可厚非的了。正是這些被約束的數據組成了Domain Model,通過這些約束,低級的數據(基本數據類型)被我們組織成了更高一級的復雜類型的數據——Domain Model Class,然后領域中的所有Domain Model交織起來最終又詮釋了整個領域。我們認為:沒有邊界的宇宙中的每一個概念都是被約束出來的,無論是“領域”還是領域中的“模型”,終極都是由規則約束出來的具有邊界的問題模型。如果沒有了約束就沒有了Model沒有了Domain,一切可以認識的東西都沒有了,只剩下了一個開放的沒有邊界的宇宙了。可見“規則”(Rule)是多么的重要,而執行規則就需要“驗證”(Validation)。

  1, 基于構造的驗證

  將驗證放在構造對象的時候,比如構造函數中或者放在屬性中。在這種情況下,當驗證失敗的時候我們一般直接拋出異常,比如拋出自定義的ValidationException異常,將錯誤信息放在自定義異常中。

 
public class Person : Entity
{

private string _name;
private DateTime _birthday;

public string Name
{

get
{
return _name;
}

set
{
if (value.IsNullOrEmpty())
{

throw new IsNullOrEmptyException("名稱不能為空");
}
_name
= value;
}
}


public DateTime Birthday
{

get
{
return _birthday;
}

set
{
if (value >= DateTime.Now || DateTime.Now.AddYears(-120) > value)
{

throw new ValidationException("出生日期不在有效的范圍內");
}
_birthday
= value;
}
}
}
測試如下:

 

 
[TestFixture]
public class PersonTest
{
[TestCase]

public void TestPerson()
{
Person person
= new Person { Name = "xuefly", Birthday = DateTime.Now.AddYears(-20) };

Assert.IsNotNull(person);
}

[TestCase]
[ExpectedException(
typeof(IsNullOrEmptyException))]
public void NameShouldNotBeNullOrEmpty()
{
Person person
= new Person { Name = "", Birthday = DateTime.Now.AddYears(-20) };
}

[TestCase]
[ExpectedException(
typeof(ValidationException))]
public void BirthdayShouldNotGreatThanNow()
{
Person person
= new Person { Name = "xuefly", Birthday = DateTime.Now.AddYears(1) };
}
}
如愿的拋出了異常。

 

  2,Validate()方法驗證

  給每一個Domain Model實現一個“Validate()”方法。在這種情況下,我們在使用Domain Model的時候主動調用該方法來驗證對象的合法性,如果驗證失敗直接拋出異常或者返回一個類似List<ValidationError>這樣形式的集合。這樣的話,約束Domain Model的“法律/規則”可以被違犯,執不執行法律的權利交給了上層的用戶,比較靈活但同時上層的用戶肩負起了責任。(就像google搞出來的那幾個“google事件”一樣,google違犯了我們的法律,確定無疑!皮球已經踢到了我們這邊,處不處罰google是我們必須要決定的事情)

  我們先定義一個用來封裝業務規則的類叫BrokenBusinessRule:

 

 
public class BrokenBusinessRule
{

public BrokenBusinessRule(string property, string rule)
{
Property
= property;
Rule
= rule;
}


public string Property { get; set; }
public string Rule { get; set; }
}
下面我們針對BlogSite來書寫驗證:其中的GetBrokenRules()方法就是Validate() Method:

 

 
public class BlogSite : Entity
{

protected bool IsValidated = false;

public string BlogTitle { get; set; }

public string DomainName { get; set; }

public BlogUser BlogOwner { get; set; }

// 該方法就是Validate() Method
public List<BrokenBusinessRule> GetBrokenRules()
{
List
<BrokenBusinessRule> brokenRules;

if (IsValidated)
{

return new List<BrokenBusinessRule>();
}


// 下面的代碼參考http://www.cnblogs.com/tristanguo/archive/2009/05/15/1457197.html 感謝tristanguo
brokenRules = new Validator<BlogSite>(this).Validate(b => BlogTitle.IsNullOrEmpty(), new BrokenBusinessRule("BlogTitle", "博客名稱不能為空"))
.Validate(b
=> DomainName.IsNullOrEmpty(), new BrokenBusinessRule("DomainName", "域名不能為空"))
.BrokenRoles;

brokenRules.AddRange(BlogOwner.GetBrokenRules());

IsValidated
= brokenRules.Count == 0;

return brokenRules;
}
}
同樣,我們使用NUnit測試一下:

 

 

 
[TestFixture]
public class BlogUserTest

{

[TestCase]

public void TestBlogUser()
{

BlogUser blogUser
= new BlogUser { LoginID="", Name="", Birthday=DateTime.Now.AddYears(1), Password=""};

Assert.IsTrue(blogUser.GetBrokenRules().Count
== 4);

}

[TestCase]

public void TestBlogUser1()
{


// Name和Birthday是在BlogUser的基類Person中定義的
BlogUser blogUser = new BlogUser { LoginID = "xuefly", Name = "", Birthday = DateTime.Now.AddYears(1), Password = "123456" };

Assert.IsTrue(blogUser.GetBrokenRules().Count
== 2);

}

[TestCase]

public void TestBlogUser2()

{


// 1:LoginID為空;2:密碼為空
BlogUser blogUser = new BlogUser { LoginID = "", Name = "xuefly", Birthday = DateTime.Now.AddYears(-20), Password = "" };// Name和Birthday是在BlogUser的基類Person中定義的

Assert.IsTrue(blogUser.GetBrokenRules().Count == 2);

}
}
 

  測試也通過了。

  3, 驗證服務

  這個類別是我通過觀察Oxite2的驗證機制分出來的,叫法不一定正確,下一篇書寫。

  歡迎加入Oxite小組一起學習:博客園Oxite小組

  4, 基于配置的驗證

  這個我現在還不是很清楚,不知道跟我想象的是一回事不,等弄清楚了再書寫吧。

   代碼這里下載:下載代碼

  參考:

  Domain Model Validation

  也談實體驗證(Entity Validation)

 

0
0
 
標簽:DDD 領域模型
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()