雖然從技術角度講,DataTable與EntityObject並沒有什麼可比性,然而,它暗示了一場革命正在悄然進行著,即使是微軟,也擺脫不了這場革命的飓風。
軟件設計思想需要革命,需要擺脫原有的思路,而走向面向領域的道路。你或許會覺得聽起來很玄乎,然而目前軟件開發的現狀使你不得不接受這樣的現實,仍然有大幫的從業人員成天扯著數據庫不放,仍然有大幫的人在問:“我要實現xxxx功能,我的數據庫應該如何設計?”這些人犯了根本性的錯誤,就是把軟件的目的搞錯了,軟件研究的是什麼?是研究如何使用計算機來解決實際(領域)問題,而不是去研究數據應該如何保存更合理。這方面的事情我在我以前的博文中已經說過很多次了,在此就不再重復了。
當然,很多讀者會跟我有著相同的觀點,也會覺得我很“火星”,但這都不要緊,上面我所說的都是一個引子,希望能夠幫助更多“步入歧途”的從業人員 “走上正路”。不錯,軟件設計應該從“數據庫驅動”走向“領域驅動”,而領域驅動設計的實踐經驗正是為設計和開發大型復雜的軟件系統提供了實踐指導。
回到我們的副標題,從DataTable到EntityObject,你看到了什麼?看到的是微軟在領域驅動上的進步,從DataTable這一純粹的數據組織形式,到EntityObject這一實體對象,微軟帶給我們的不僅僅是技術框架,更是一套面向領域的解決方案。
.NET 4.0來了,隨之而來的是實體框架(EntityFramework,簡稱“EF”),在本系列文章中,我將結合領域驅動設計的實踐知識,來剖析EF的具體應用過程,當然,現在的EF還並不是那麼完善,我也非常期待能夠看到,今後微軟能夠繼續發展和完善EF,使其成為微軟領域驅動工具箱中的重要角色。
先不說EF,首先我們簡要地分析一下,作為一種框架,要支持領域驅動的思想,需要滿足哪些硬性需求,之後再仔細想想,.NET EF是否真的能夠很好地應用在領域驅動上。
首先需要能夠正確對待“領域模型”的概念。領域模型與數據模型不同,它表述的是領域中各個類及其之間的關系。類關系是多樣的,比如組合、聚合、繼承、實現、約束等,而數據模型不是一對多,就是多對多。從領域驅動設計的角度看,數據庫只不過是存儲實體的一個外部機制,是屬於技術層面的東西。數據模型主要用於描述領域模型對象的持久化方式,應該是先有領域模型,才有數據模型,領域模型需要通過某種映射而產生相應的數據模型。因此,框架必須支持從領域模型到數據模型的映射。
EF不僅支持從領域模型生成數據庫的DDL,而且支持從數據庫結構來生成“領域模型”。我倒是覺得後者可以去掉,因為從數據庫得到的已經不是領域模型了。你會問為什麼,我可以告訴你,單純的數據是沒辦法描述領域概念的。比如:你的數據庫裡有一個表叫做“Customer”,在通過數據庫結構生成“領域模型”的時候,Visual Studio當然會幫你生成一個“領域對象”叫做Customer,但如果我把這數據表的名字改為“abc”,雖然裡面還是存的客戶信息,但EF並不知道這一點,也是照樣生成一個“abc”的類,而這樣的東西能算是領域對象嗎?因此,數據依賴於實體,是實體的狀態,離開實體的數據毫無意義
上圖中標記的內容,根據領域驅動設計的思想,是不應該保存的。然而.NET是一個產品,它需要顧及到各種需求,因此,“從數據庫生成模型”的功能也將被保留下來
對“聚合”概念的支持。聚合是一系列表述同一概念的相互關聯的實體的集合,比如銷售訂單、銷售訂單行以及商品,這三個實體可以整合成一個聚合,銷售訂單則是聚合的根。關於聚合的問題將在後續文章中討論。為什麼引入聚合?這是領域驅動設計處理大型軟件系統的一種獨到的方式。軟件系統的實體對象可能非常多,之間的關系也可能錯綜復雜,那麼,當我們需要從外部持久化機制“喚醒”(或者說讀取)某個實體對象的時候,是不是需要將它以及它所關聯的所有對象都一並讀入內存呢?當然不是,因為我們只需要關注整個問題的某個方面。比如在讀取客戶數據的時候,我們或許會將這個客戶的所有訂單顯示出來,但不會將每個訂單的訂單行也全部讀出來,因為現在我們還沒有決定是否真的需要查看所有的訂單行。
EF目前不支持聚合概念,所有的實體都被一股腦地塞進 ObjectContext對象的EntitySet屬性當中,不過這沒關系,接下來的文章我會介紹如何在EF中引入聚合的概念
值對象支持。了解領域驅動設計的朋友都知道,領域模型對象分為實體、值對象和服務。以前的LINQ to SQL不支持值對象,很郁悶,現在好了,EF支持值對象,其表現為ComplexType類型。在這裡我想提兩點,首先,EF還不支持枚舉類型,不要小看枚舉類型,與整型類型相比,它能夠更好地表達領域語義,比如銷售訂單的狀態可以有 Created,Picked,Packed,Shipped,Delivered,Invoiced,Returned和Cancelled,如果用 0,1,2,3,4,5,6,7來表示,你就會看不懂了,因為這些整數都不是“自描述”的,你需要去讀額外的文檔來了解它們的意思。其次就是我不太喜歡將 ComplexType定義為引用類型,我覺得用值類型就會更加輕量。當然,我不反對使用引用類型,畢竟EF也是出於框架設計的需要
實體不僅有狀態,還應該有行為。這是很自然的事情,我們應該使用“富領域模型”,而不僅僅是搞一些 POCO加一些getter/setter方法。因為對象本身就應該有行為,這才能夠以自然的方式描述現實領域。很可惜,EF的Entity Data Model Designer只支持對象狀態(屬性)的圖形化定義,而不支持行為定義,這也給EF帶來了一個硬傷:沒法定義行為的重載和重寫,而這些卻恰恰是業務邏輯的重要組成部分。我更希望在下一代EF中,能夠看到的是“Entity Model Designer”,而不是“Entity Data Model Designer”。當然,我們也可以通過使用C#中部分類(partial class)的特性,向實體注入行為,這並不影響實體對領域概念的描述性。
最糟糕的就算是,EF居然支持從數據庫的存儲過程來生成實體對象的方法。這從根本上把技術問題和領域問題混為一談,我認為這個功能也可以去掉,因為存儲過程面向的是數據,而實體方法面向的是領域。有關存儲過程的問題,我在後面的文章中也會提到
從上面的描述,我們對EF的功能有了個大概的了解,接下來的系列文章,我會和大家一起,一步步地探討,如何在EF上應用領域驅動設計的思想,進而完成我們的案例程序。