首先把圖書館系統的背景說明一下吧﹕公司每個成員通過局域網登錄圖書管理系統﹐然后預借書籍﹐圖書管理員收到預借信息后﹐核準借閱﹐并通知借書人前來領書﹐告知相關事項。
領域模型的價值不在于它的設計優美(它只是一些對象﹐最重要的也就是對象之間的關系)﹐而在于它體現了系統的核心價值。什么是系統的核心價值呢?我想我們的圖書館系統和華爾街的一個商業系統本質的區別應該不是用了什么語言﹐OO還是過程﹐用了什么數據庫。因為這兩個系統使用什么語言﹐采用什么方式﹐利用什么DBMS﹐都是不能本質區別這兩個系統的價值的。
那什么才是系統真正的價值呢?
那就是﹕系統能為使用提供什么服務﹐以及提供的質量(即如何服務﹐這也是系統內部的我們要做的事情)。
那什么體現的系統的服務呢?系統的運行方式﹐系統的運行過程﹐系統的業務邏輯。
以數據庫為中心的開發方式﹐將所有業務邏輯都放在了數據庫﹐并結合系統的代碼來保證業務邏輯的實現。
而面向對象﹐則其表現點則是直接在對象本身上﹐在于對象之間真正的交互過程﹐結果也是保留在對象的屬性和對象與對象的關系中。
從以上方式可以看出﹐無論以數據庫為中心的開發如何的OO﹐如何多的設計模式﹐架構體系如何優美﹐它始終離不開數據庫。它的OO只是一種手段﹐而非目的。(大家別激動﹐我始終沒有提這種方式不好﹐只是實現系統的兩種方法而已)
面向對象則不同﹐它將邏輯直接存在于對象上﹐這與現實情況是吻合的﹐這也是面向對象引起這么大熱潮的根本原因(我想﹐不明白這一點﹐您會在ORM,AOP等等方面不知其所以然的)。
領域模型是一種思維﹐是一種方法,是在系統分析階段使用﹐而不是程序員在自己的代碼中進行純設計時的工具。我們不是為了OO而領域﹐不是為了最終要新增數據庫而領域﹐這也是為什么在沒有理解領域模型本質時﹐使用它進行片斷式的代碼編寫﹐得不到任何好處的原因。想想你在編碼中弄出的那些用戶﹐圖書對象﹐它在系統中的位置是什么?您不能否認它就是為了讓新增數據庫時更方便吧?
領域模型的建立決不是像如何實現某些純軟件邏輯而進行的純軟件設計。(我這里提出的純軟件設計﹐指的是如何在設計中體現擴展性﹐靈活性﹐可維護性而進行的設計)。而是為了能夠分析系統提供的服務而產生的一種思維結果。
系統分析員在接手一個系統后﹐首先要做到的事情就是得出系統的服務和服務場景。也就是我們經常所講的用例(use case)
可惜的是﹐很多使用者一直將用例的作用和價值沒弄清楚。我想如果沒有真正理解用例的作用時﹐您的用例分析將會一直停留在和別人Demo系統之上﹐提供一個漂亮的圖形好說話而已﹐而不會對我們的系統開發有任何實質作用。
用例表示的是使用系統的一個場景﹐其本質在于詳細描述了系統用戶(actor)與系統是如何交互的﹐以及交互的后果是什么﹐詳細而完善的用例將指導您進行系統開發的全過程?
還以圖書管理系統的借書為例﹐這是用例的文本描述(也是真正的用例)﹕
系統﹕顯示圖書館的所有書籍類別
用戶﹕瀏覽并選擇一個書籍類別
系統﹕顯示這個書籍類別的所有書籍
用戶﹕瀏覽并選擇一本書籍
系統﹕顯示書籍的詳細信息
用戶﹕借閱書籍
系統﹕辦理預借手續
用例后果﹕產生一份預借記錄。并將預借記錄與書籍關聯。
領域模型如何提取的具體辦法就不講了﹐其實在基本的面向對象的書籍中都有提到過﹐一個最簡單的方法就是從語句中的名詞找出領域對象。
經分析﹐這里的領域模型有﹕圖書館﹐書籍類別﹐書籍﹐預借記錄(所有類別﹐所有書籍﹐詳細信息這些也是名詞﹐但是經過分析﹐它們分別是屬于圖書館﹐書籍類別﹐書籍的屬性﹐而不是領域模型)

領域模型中最重要的是領域模型之間的關系(當然還有屬性)﹐我們的系統的邏輯也是體現在領域對象與對象之間的關系上(如您借了一本設計模式的書﹐在系統中就是設計模式這本書對象與借閱記錄對象關聯)。
得到領域模型后﹐再根據用例﹐我們分析出領域模型對象的交互過程。
請注意﹐這里的顯示****,并沒有UI過程﹐實際上就是返回這個對象的某個屬性﹐至于如何顯示﹐那是UI層的故事。
根據”借閱”用例的描述﹐我們不難得到用例的業務過程的代碼表示了﹕
借閱記錄 aaa = new 借閱記錄
設計模式.借閱記錄 = aaa;
很簡單﹐就是產生一個借閱記錄對象﹐并將它與書籍對象關聯﹗
依照迭代過程﹐我們對所有的用例都完成這樣的建模過程。系統也就完成了。
在不斷的迭代過程中﹐有很多屬性呀﹐過程呀﹐都會逐步地由淺至深﹐最終完全符合了用戶的要求。
在完成這些過程之后﹐一個系統的領域模型就建造完畢。所有系統的本質屬性都體現在這些領域模型的活動過程之中。
一個系統要變更業務邏輯﹐我們只要針對領域模型作變化即可﹐再也不需要抱怨變化了。
終于要考慮系統其它部分了。
數據庫﹐日志﹐郵件通知﹐當然還有UI(在之前的評論中有詳細描述﹐感興趣的朋友可以去找找這幾天的評論)
這些都屬于系統核心以外—領域模型以外的東西。
這些模塊與領域模型的接觸在哪里呢?當然不能在領域模型內部﹐您不能在借閱邏輯中加入這些對象。
只有一個地方﹐那就是邊界。
系統邊界﹐即域控制器﹐即上面順序圖的”系統”對象所處的位置﹐系統對象除了與領域模型﹐用戶打交道以外﹐它還會與系統的其它模塊交互。如持久化系統(您不能因為客戶由sql server要求轉向oracle而去更改業務邏輯吧)﹐日志系統(您不希望修改日志規則而影響業務邏輯吧)﹐信息通知系統(您不能因為用戶要求由郵件通知改為短信通知就修改領域模型吧)。
將領域模型獨立于持久化系統的真正原因﹕系統業務邏輯與如何存儲數據以便使系統正常持續運行無任何關系。
當然﹐還有設計原則也會支持這種做法。持久化系統依賴領域模型時﹐只有領域模型本身變動才需要修改持久化過程﹐反之則不然﹐這正是依賴倒置原則的一個體現。
我稍微寫一下域控制器的代碼實現過程吧﹕
Public void 借閱()
{
//這個借閱處理者是純粹的軟件對象﹐其存在的目的就是將借閱的實際處理過程獨立出來
借閱處理者 處理者 = new 借閱處理者(當前書籍﹐當前登錄人姓名);
Bool successful = 處理者.借閱() //這個方法主要就是上面的那2行代碼
If(successful){
持久化系統.add(當前書籍);
日志系統.add日志(當前當籍,”借閱”)
郵件系統.發送郵件(當前書籍.當前借書人姓名)
}
}
Void 持久化系統.add(書籍 當前書籍)
{
借閱關系 Bbb = new 借閱關系
Bbb.id = 產生ID()
Bbb.圖書ID = 當前書籍.id;
Bbb.借閱人 = 當前書籍.借閱記錄.借閱人姓名
Bbb.借閱時間 = 當前書籍.借閱記錄.借閱時間
Bbb.Save();
}
其它模塊處理過程類似。
從這里可以看出﹐業務過程是系統的核心﹐其它模塊都是依賴于它而存在。
如果有ORM﹐它使用的地方就是這里了。
在現實系統中﹐我在if(successful)這里作了一些純軟件設計﹐如利用C#具有Event特性﹐將借閱方法后公開出一個事件﹐這樣我在再要添加什么外圍模聲時﹐只要響應事件就可以了﹐不需要再來動這個方法
另外一個不容回避的問題﹐統計報表﹐雖然循環內存中的對象﹐理論上完全可以抓出所有的數據。
但是對于這一類問題﹐我就不麻煩領域對象了(其實它與領域模型﹐業務邏輯也真的沒有關系)﹐而是將它獨立實現﹐直接調用持久化模塊﹐下sql抓資料。
還有一個實際的問題﹐就是對象的加載。理論上﹐每次系統重啟后﹐都要將所有內存中的對象全部還原﹐構建領域環境。
然而對于大部分實際運用的系統﹐這是不可能的﹐如這里的圖書館對象﹐它可能有上千本書﹐因此一次加載肯定不行﹐我使用的方法是﹐利用Proxy﹐繼承領域模型﹐延遲加載。關于這一點﹐就屬于純設計問題了﹐如何實現﹐歡迎大家有興趣再探討﹗
(請教一下大家用什么方法發表blog呀﹐我每次從word中貼過來好痛苦)
領域驅動真實實踐時還會遇到很多問題
目前我的看法是:使用數據庫,才是真正的問題解決之道
浙公網安備 33010602011771號