引子
自從去年接觸DDD以來,閱讀了大量的相關書籍,看了園子里面很多DDD方面的文章,也在實際項目中也有意無意的使用DDD的一些思想和方法。但總覺得這些知識太分散,沒有給自己思想帶來質的升華。觀摩了園子里面很多DDD的例子:
等等還有很多,這些例子的質量都很高,圖文並茂,一堆讓人流口水的代碼示例。但每次看完例子后,我的迷茫就會又加深一分。因為我覺得很多博文都只是打着DDD的幌子,還是按自己的原來理解方式寫代碼而已。並沒有給大家深入剖析DDD。別忘了,DDD是領域驅動設計,而不是領域驅動開發。在完成這些例子過程中,太重視出成果,出一個實際的代碼項目,而忽視了設計,即使有些設計,也只是停留在戰術建模的階段,並沒有站在戰略建模的高度來對需求進行分析,通常給出一個UML類關系圖,一個分層架構圖就開始突突突地來一堆代碼,最后還不忘貼兩個漂亮的UI截圖,引起無數粉絲追捧(額~~他們的粉絲中也有我,看來可以把我拉黑了)。用沃恩.弗農大神的話,這樣做只能算DDD-Lite,離真正的DDD還有段距離。
於是我有了寫這篇文章的想法。
所謂的戰略建模有兩點:界限上下文(Bounded Context)、上下文映射圖(Context Mapping)。
來看一下它們的定義:
限界上下文:它是一個限定邊界的環境,在該環境中,每一個模型的概念(包括它的屬性和操作)都具有特殊的含義。它是戰略建模的核心。 上下文映射圖:通用使用框圖或代碼的方式來展現限界上下文之間的集成關系。 |
它們為什么重要,我以dax.net大大的一個例子來說明,來看一下他的一篇文章EntityFramework之領域驅動設計實踐(三) ,文中內容大概是這樣安排的:
- 首先介紹他的例子:一個簡易的銷售系統;
- 給出UML實體框圖;
- 然后突突的給出一堆代碼;
這是一個典型的戰術建模的例子。而且該文中有一句極其誤導DDD新人的話,我不得不吐槽一下:
上面的模型表述了領域模型中各個實體及其之間的關系。我們先不去討論整個系統的業務會是什么樣的,我們先看看EF是如何支持實體和值對象概念的。 |
它直接讓我們這些技術狂熱愛好者們瞬間偏離了DDD以業務為核心,不依賴具體技術實現的初衷。DDD本來就是引導我們來解決業務上的問題,而不是來讓我們炫耀新技能的。如果只是介紹EF,那這篇文章是篇合格的文章。但,如果把它作為DDD的文章,那它就是個反面教程。
很不幸,本文開始的幾個鏈接也都被我划到反面教程之列,而且類似的文章園子里面還有很多。這些文章用來教大家寫代碼可以,但不能作為DDD的教程。
我的DDD設計之道
就像前面說的,我們進行DDD的第一步,不應該是急着去創建實體模型,而應該站在更高的層面去理解需求,划分領域。這里我就以dax.net這個簡易的銷售系統為例。首先我們來看看那篇文章中的模型圖(注意:這里不是用這個模型圖,而僅僅是參考一下用來分析業務,就當這個模型圖是會說話的客戶吧!):
必須坐下來和客戶(就是上面這個模型圖)好好談談了,以下為談話內容:
把對話內容總結一下,我們可以看出需求業務大概是這樣的:
- 客戶可以在本系統進行支付,並且可以使用多信用卡支付;
- 為客戶生成訂單;
- 在現有的訂單基礎上可以進行退訂;
- 管理客戶信息,主要是信用卡信息管理;
- 管理產品信息;
- 管理產品分類信息;
來把這幾條業務進行划分,得到原始領域結構圖:
上圖只是對領域進行了划分及確定限界上下文,各限界上下文的關系下面會有介紹。這里共4個限界上下文:客戶上下文,訂單上下文,支付上下文及產品上下文。這里有以下幾點要特別說明:
- 通過對領域的划分,優化了原有系統設計中Item實體即表示訂單項目,又表示產品的混淆的定義。在訂單上下文中,沒有產品這個概念,只有訂單項,訂單項的屬性數據(主要是產品名稱,單價)會在生成訂單項實體時從產品上下文中的產品獲取;
- 支付上下文中沒有"客戶"這個概念,只有"帳戶"這個概念(圖中畫成帳號,在此糾正一下),同訂單中的訂單項,"帳戶"的屬性也會在生成帳戶實體時從其他上下文中獲取,這里是從客戶上下文中獲取,"帳戶"是客戶的一個子集;
- 隨着業務的增長,可以把退訂業務從訂單管理子域中划分出來,作為一個單獨子域,這里暫時先不考慮;
可以看出,在確定子域及限界上下文后,一些容易混淆的概念會逐漸得到清晰的描述,這樣可以方便開發團隊、業務人員及客戶之間的交流,而且還為我們開發時划分項目功能提供最直接的依據。
我們進一步對該圖進行優化。來看一下限界上下文之間的關系,即上下文映射圖:
圖中的實線連接,表示兩端的限界上下文之間存在聯系。線上標注的U/D表示上游/下游。通常情況下:上游的限界上下文會為下游提供訪問接口(或服務),下游使用一個防腐層獲取從上游接口傳過來的數據,然后轉化成本限界中使用的實體。
舉個例子:產品上下文是訂單上下文的上游。當用戶進行產品選購時,會向訂單里面添加訂單項(丫的,這里沒設計購物車~~):
class Order { private IList<Item> _items = new List<Item>(); //支付上下文中的防腐層 private IACLService _iaclService; public Order(IACLService aclService) { _iaclService = aclService; } /// <summary> /// 添加訂單項 /// </summary> /// <param name="productid">產品ID</param> /// <param name="count">產品個數</param> public void AddItem(string productid, int count) { Item item = _iaclService.GetItemFor(productid); item.Count = count; _items.Add(item); } }
在偽實現中,添加訂單項的函數AddItem參數用的是產品的id,而不是產品的具體信息,這樣就可以實現訂單項與產品的解耦。假設我們的產品管理子域是通過WCF用服務的方式實現,防腐層就可以通過訪問服務來獲取該產品,然后把它轉化成一個Item。從而實現訂單項的添加。
DDD方面的代碼實現會在近期放出,敬請期待!
后記
本文是站在個人理解角度去闡述DDD,在實現過程中也沒有涉及CQRS/AES等概念,只是想說明一下,如果使用DDD,請站在業務的角度,而不是技術的角度。
關於學習DDD,強烈建議學習netfocus的ENode框架!
本文如有冒犯之處還請海涵,歡迎拍磚,不過你要是進行人身攻擊,我也只能在心里畫個圈圈詛咒你一下。