主題:應用領域驅動開發(Applying Domain-Driven Development)
Domain Model是MVC程序的"心臟",其他的一切,包括Controllers和Views僅僅是用來跟Domain Model交互的一種方式,ASP.NET MVC并沒有限制使用在Domain Model上面的技術,我們可以自由的選擇跟.net framework交互的技術,并且這樣的選擇是非常多的。不僅如此,ASP.NET MVC為我們提供了基礎的架構和約定來幫助Domain Model里面的Classes跟Controllers和Views的聯系,也包括跟MVC自己的聯系。
它們有三個關鍵的功能,如下所示:
a.模型綁定(Model Binding):是自動使用HTML表單提交的數據來組織領域對象(Domain Objects)基礎的約定。
b.模型元數據(Model metadata):描述了Model Classes對.net的所要表達的意思。舉例而言,就是給屬性賦予一個人容易理解的表述或者是對屬性呈現時的一些提示。asp.net mvc能夠自動的識別并對Model Classes合理的呈現到Views里面。
c.驗證(Validation):能夠在模型綁定時被執行,并且能夠應用被定義為元素據的那些規則。
如果你對于模型綁定(Model Binding)的理解也跟我一樣還不是很清楚,也沒有必要著急,更不要放棄對MVC的學習,因為書的后面章節會有詳細的講解 ,呵呵。現在我們先將ASP.NET MVC的實現放到一邊,單純思考下領域建模(Domain Modelling),下面使用.net和sql server創建一個簡單的競拍程序的領域模型。
一,創建一個簡單的領域模型
下面是要創建的競拍程序的類圖
上面的模型包含了一個Members的集合,每一個Member會有一個Bids集合,每一個Bid對應一個Item,每一個Item可以有多個來自不同Members的Bids
實現我們自己的domain model并作為一個獨立的組件其中一個關鍵的地方是我們選擇的語言和術語,這個不是我們的編程語言,而是領域建模的通用的語言。這種語言是開發人員和領域的專業人員都知道的,這樣主要是為了這兩種人能流暢的交流,而這卻是至關重要的。當領域的專家們不了解建模的一些概念時,我們應該針對使用的術語達成一個共識,這個共識就是創建一種通用的語言并貫穿在整個領域建模過程中。這樣做有很多的好處。
1.首先,開發人員傾向于使用編程語言,比如類名,數據庫等等名詞來表達。而業務專家們是不懂這些的,他們也不需要懂。業務專家知曉一些技術方面的知識是一件非常危險的事情,因為他們會經常根據自己對技術的理解來不斷篩選他們的需求,這也就意味著需求會頻繁的更改,進而導致開發人員也不知道業務專家的真實需求到底是什么。創建通用語言的方法能夠幫助我們避免在一個應用程序里面過度的泛化需求,程序員傾向于建立每一個可能業務實際模型,而不是具體到某一個業務需求。
2.在通用語言和領域模型之間的這種連接不應該是非常膚淺的而是向DDD(Domain-Driven Design)專家所建議的那樣:對通用語言的任何變化都會導致Model的變化。假如我們讓建模跟業務領域不同步,我們就需要建立一種從Model到domain映射的中間語言,從長遠來看,這種做法會導致災難。為此,我們將創建一個會兩種語言的特殊人類,他們隨后就開始篩選需求,這卻是建立在他們對兩種語言都不完全理解的基礎之上,當然這樣的后果可以想象。
二,聚合與簡化
上面的圖,為我們提供了一個很好的建模起點
但是上面圖示的模型并沒有提供用C#和SQL Server實現Model的任何有用的幫助,會有不少的問題困擾我們:
1.如果我們load一個Member進入內存,也是不是應該load Member的Bids以及相關的Items進入內存呢?
2.如果我們這樣做了,我們是否需要將這個Item其他的bids也load進內存,并且也將做這些Bids的Members一同load進內存呢?
3.當我們刪除一個對象時,我是否應該刪除相關的對象呢?如果是,又有哪些呢?
4.如果我們選擇用文檔存儲代替關系型數據庫來持久化,那哪一個對象的集合應該呈現到同一個文檔呢?
所有以上的問題,我們不知道作何解答,并且我們的領域模型也沒有給我們任何答案。
回答這些問題的DDD方式是將Domain Objects分配到組里面,這種方式稱為聚合(aggregates)。
下圖很好的展示了怎樣聚合在我們這個競拍程序里面的領域模型,如下所示:
一個聚合的實體組將若干領域對象聯合(Together)到了一起,有一個根實體被用來標識整個聚合,它在驗證和持久化操作里面充當了"boss"的角色。在數據變化時,我把聚合當作一個單元來統籌處理,所以我們需要創建呈現在領域模型上下文里面有意義的關系的聚合,并且創建跟實現業務過程一致的邏輯操作。也就是說,我們需要通過分組對象來創建聚合,而這些對象是可以作為一個組被改變的。
一個非常重要的DDD規則是,在一個聚合實例范圍外的對象,只能通過對根實體(root entity)的引用來持久化,而不是引用在聚合里面的對象。這條規則強化了將聚合里面的對象作為一個單元來對待的概念。在本例子里面,Members和Items都是聚合的根,而Bids只能在作為它們聚合根實體的Item的上下文(the context of Item)里面被訪問。Bids可以引用Members(根實體),但是Members不能引用Bids(不是根實體)
聚合的一個好處是簡化了對象跟領域模型之間的關系,通常這樣能夠幫助我們對需要建模的領域的本質的理解。本質上講,創建聚合約束領域模型和對象之間的關系使得這種關系更加接近于現實領域里面存在的關系。下面是用C#來表達的,如下所示:
public class Member {
public string LoginName { get; set; } // The unique key
public int ReputationPoints { get; set; }
}
public class Item {
public int ItemID { get; private set; } // The unique key
public string Title { get; set; }
public string Description { get; set; }
public DateTime AuctionEndDate { get; set; }
public IList<Bid> Bids { get; set; }
}
public class Bid {
public Member Member { get; set; }
public DateTime DatePlaced { get; set; }
public decimal BidAmount { get; set; }
}
從上面的代碼可以看出,我們很容易就捕捉到了Bids和Members之間的單向關系的本質,當然我們也可以建立一些其他的約束。例如:Bids是不可變的,這也是符合實際的。應用聚合能夠幫助我們建立更加有用,更加精確的領域模型,也能夠讓我們用C#熟練的實現。
一般來講,聚合為一個領域模型增加了結構化和精確化。這也使得應用驗證變得容易(根實體負責驗證聚合里面所有對象的的狀態),這很明顯也是持久化的單元。由于聚合的本質就是領域模型的原子單元,它們也能夠適用事務管理的單元和級聯從數據庫刪除的單元。
另一方面,聚合常常是人為加上限制。聚合(Aggregates)的概念能夠很自然的從文檔型數據庫得到,但它不是sqlserver本身的概念,也不是存在大部分ORM工具里的概念,為了很好的實現它們,我們的團隊需要科學有效的溝通。
三,定義倉庫(Defining Repositories)
在某些時候,我們需要給領域模型添加持久化,這通常是通過關系型,對象型,或文檔型的數據庫來做。持久化不是我們領域模型的一部分,它是一個獨立的關注點,這也就意味著我們不能將持久化的代碼跟定義領域模型的代碼混合到一起。解決這個問題通常的方式就是定義一個倉庫(Repositories)
Respositories是基于數據庫(可能你選擇的是文件存儲等等)層面的對象呈現。領域模型通過調用定義在Repositories的方法來間接的存儲和查詢數據庫,這使得我們的Model可以獨立于持久化的實現。這樣約定就是為每一個聚合(Aggregates)定義單獨的數據模型。在我們的競拍程序里面,我們可以創建2個Repositories,它們分別是針對Members的Repository和針對Items的Repository。注意這里,我們并不需要創建針對Bids的Repository,因為Bids會作為Items聚會持久化的一部分)。
下面是定義的兩個Repositories,如下所示:
public class MembersRepository {
public void AddMember(Member member) { ... }
public Member FetchByLoginName(string loginName) { ... }
public void SubmitChanges() { ...}
}
public class ItemsRepository {
public void AddItem(Item item) { ...}
public Item FetchByID(int itemID) { ... }
public IList<Item> ListItems(int pageSize,int pageIndex) { ...}
public void SubmitChanges() { ... }
}
特別需要注意:Repositories僅僅針對Loading和Saving Data.它們不包含任何其他的邏輯。
今天的筆記做到這里,我也是剛學習MVC,做筆記是為了鞏固和加深理解,當然如果能給到那些跟我一樣的初學者一點點的幫助,我就非常高興了。筆記里面肯定會有我理解不對的地方,還請路過的大牛們多多幫助指導。謝謝!
祝路過的朋友工作順利!
晚安!