從DataTable到EntityObject
雖然從技術角度講,DataTable與EntityObject並沒有什麼可比性,然而,它暗示了一場革命正在悄然進行著,即使是微軟,也擺脫不了這場革命的颶風。
軟體設計思想需要革命,需要擺脫原有的思路,而走向面向領域的道路。你或許會覺得聽起來很玄乎,然而目前軟體開發的現狀使你不得不接受這樣的現實,仍然有大幫的從業人員成天扯著資料庫不放,仍然有大幫的人在問:“我要實現xxxx功能,我的資料庫應該如何設計?”這些人犯了根本性的錯誤,就是把軟體的目的搞錯了,軟體研究的是什嗎?是研究如何使用電腦來解決實際(領域)問題,而不是去研究資料應該如何儲存更合理。這方面的事情我在我以前的博文中已經說過很多次了,在此就不再重複了。
當然,很多讀者會跟我有著相同的觀點,也會覺得我很“火星”,但這都不要緊,上面我所說的都是一個引子,希望能夠協助更多“步入歧途”的從業人員 “走上正路”。不錯,軟體設計應該從“資料庫驅動”走向“領域驅動”,而領域驅動設計的實踐經驗正是為設計和開發大型複雜的軟體系統提供了實踐指導。
回到我們的副標題,從DataTable到EntityObject,你看到了什嗎?看到的是微軟在領域驅動上的進步,從DataTable這一純粹的資料群組織形式,到EntityObject這一實體物件,微軟帶給我們的不僅僅是技術架構,更是一套面向領域的解決方案。
.NET 4.0來了,隨之而來的是Entity Framework(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上應用領域驅動設計的思想,進而完成我們的案常式序。本系列文章均為我個人原創,或許在某些問題上你會有不同意見,不要緊,你可以直接簽寫留言,或者發郵件給我,期待與你的探討,期待與你在軟體架構設計的道路上共同進步。