天天看點

IDDD 實作領域驅動設計-由貧血導緻的失憶症

後來,在探讨的過程中,有很多我不知道的概念被讨論,比如 CQRS、六邊形架構、事件溯源等等,我對這些概念是一竅不通的,像六邊形架構,我隻知道六邊形有六個邊(莫笑),這讓我意識到,你隻了解經典 DDD 架構,會讓你自己陷入一些困境,有時候不是你自己的設計問題,而是你的眼界被遮掩住了,你需要去探尋自己視野之外的東西,這樣才會有所進步。

其實,學習 DDD 最好的方式,就是用最真實的實際案例去運用,在運用的過程中,去發現問題并進行探讨學習,這樣雖然會很艱辛,但收獲也是巨大的,除此之外,你還會發現另一個問題,就像在建高樓大廈的時候,雖然樓房的設計是世界最高水準,但是地基打不穩,空有一張設計圖紙又有什麼用呢?

昨天晚上,我大概讀了第一章《DDD 入門》的前半部分,有很多内容我覺得還是蠻有意思的,我希望可以把這些東西記錄下來,以防備自己的“健忘症”。

先來看書中提到的兩個病例測試:

你的領域對象中是不是主要是些共有的 getter 和 setter 方法,并且幾乎沒有業務邏輯,或者完全沒有業務邏輯-對象嘛,主要就是用來容納屬性值的?

軟體元件經常使用的領域對象是否包含了系統主要的業務邏輯,并且多數情況下你需要調用那些 getter 和 setter?你可能會将這樣的客戶代碼稱為服務層(Service Layer)或者應用層(Application Layer)代碼,也或者,如果這描述的是你的使用者界面,請回答“Yes”,然後好好檢討一下,告誡自己一定不要再這麼做了。

第一個問題是領域對象的定義,第二個問題是領域對象的調用,你的回答是什麼?一個 Yes、一個 No?如果你是這樣的回答,作者給你這樣的分析:你可能是在自欺或者患上了由貧血症導緻的神經系統紊亂。哈哈,作者還蠻調皮的,回歸正題,考慮這兩個問題的時候,你可以和你正在做的項目進行對比考慮,是不是對你産生了一些共鳴呢?有人可能會說:唉呀媽呀,這不是我“萬能”三層架構裡面的 Model 層和 BLL 層嘛?如果你這麼想的話,對你的最終确認結果是:先生,你患上了貧血症,而且還“貧”的不輕呢。

IDDD 實作領域驅動設計-由貧血導緻的失憶症

上面是從富有行為對象到貧血對象的時間線,凡事都有存在的理由,像貧血對象也是,它也是由多種因素導緻并演化而來的,在作者叙述的這一部分内容中,我覺得主要概括為兩個因素:Microsoft Visual Basic 開發方式和早期 ORM 暴露共有屬性,ORM 暴露共有屬性這個我不是很懂,但是 Microsoft Visual Basic 開發方式對我還是蠻有影響的,記得在上大學的時候,老師講 Web Forms 和 Windows Forms 的課程,都是一拖一個控件,然後再設定控件的屬性,這樣一個項目基本就完成了,從那時候開始,“屬性”的概念就慢慢培養起來了,做一個項目之前,會先把一系列的 Model 屬性設計好,按照需求下面就是對這些屬性值的修改,最後就是把這些 Model 儲存的資料庫中,過程就是這麼個過程,有錯嗎?沒有,但是呢,好像建設一棟摩天大樓的設計不應該這麼簡單吧?我們看下面的代碼(PDF 檔案,不能複制,隻能純手打):

當時,看到這個 saveCustomer 方法中的代碼,我哈哈大笑了三聲,笑的不是别人,而是我自己,因為我之前寫過比這個 saveCustomer 方法還多的代碼,那個看起來更加臃腫,之前開發的是快遞業務系統,一個表多的話有近上百個字段,那修改這個表的屬性,就是像上面的代碼一樣,不同的是,我的比這個更多,一坨一坨的。比如上面,不管是位址變了沒變,你都是使用的 saveCustomer,那這個方法到底是什麼含義呢?你也說不清楚,因為它看上去是那麼的“萬能”,不過,也确實如此。因為你說不清一個方法的具體作用,這樣導緻的結果就是失憶症,原因是由貧血模型産生。

舉個例子,有一天,業務人員告訴 DBA(業務實際掌握人),要去掉 Customer 中的一個屬性,然後 DBA 就在 Customer 表中,把這個屬性對應的字段去掉了,但是 DBA 并沒有告知你,因為他覺得沒必要(你又不懂業務),但是,你發現項目突然報錯了,然後你就各種排查,最後發現是 saveCustomer 方法裡面抛出的異常,然後你就開始一個一個比較 Customer 模型屬性和 saveCustomer 表字段,發現原來是少了一個字段,然後,你就和 DBA 幹了起來。。。

以上純屬虛構,如有雷同,那就雷同吧,針對 saveCustomer 出現的問題,作者簡要總結了下:

saveCustomer() 業務意圖不明确。

方法的實作本身增加了潛在的複雜性。

Customer 領域對象根本就不是對象,而隻一個資料持有器(data holder)。

以上的三大問題,就是導緻“失憶症”發生的根本原因。

在領域驅動設計中,通用語言是非常重要的一個概念,在書中的第一章節中,作者也反複提到這個概念,并進行了詳細解釋,我之前認為通用語言就是代碼,領域專家和開發人員都可以看懂的代碼,但這種了解是片面的,領域專家是業務專家,他又不是開發人員,怎麼能看懂代碼呢?來看幾個對話:

很明顯,通用語言是一種業務語言。

抱歉,不是。

通用語言必須采用工業标準術語。

不完全是。.

通用語言是領域專家專用的。

對不起,不是。

通用語言是團隊自己建立的公用語言,團隊中同時包含領域專家和軟體開發人員。

對了。

在了解通用語言之間,還有個問題也容易混淆,至少我是這樣的,那就是設計和實作的差別,有人就說了:很簡單啊,這有什麼好混淆的,設計就是我們畫的業務流程圖或者是 UML,實作就是代碼。仔細一想,好像也确實是這樣,但是在領域驅動設計中,領域模型的設計是通過與領域專家進行讨論确定的,畫的各種設計圖,并不是領域驅動的設計,而隻是我們建設讨論的一種方式而已,那設計是什麼?設計其實就是代碼,代碼就是設計,是以,在領域模型的設計中,不要把設計和實作的概念區分開,他們其實是一個概念而已。

在上面對話的第四點中,通用語言是團隊自己建立的公用語言,什麼意思呢?公用的意思,就是所表達的内容領域專家和開發人員都懂,語言其實不是說話的語言,中文?英文?都不是,也不是代碼語言,它其實是溝通的一種方式,大家都可以了解的一種方式,一個團隊有一個屬于自己的公用語言,範圍是僅限于團隊内容,可能這個公用語言在其他團隊就不适用了,也就說,它是團隊成員自己建立的,當然也不是一下就可以建立出來的,是一步一步進行完善,需要每一個領域專家和開發人員的參與。

不管怎麼了解,公用語言概念中,有一點是非常重要的,那就是溝通,可能說多了不好了解,作者就舉了一個示例:

IDDD 實作領域驅動設計-由貧血導緻的失憶症

你會發現,業務描述的不同,最後實作的代碼就會千差萬别,也就是說開發人員和領域專家的溝通很重要,當然開發人員的了解能力也很重要,很多的方方面面,就組成了通用語言的概念。

如果你不知道怎麼了解通用語言,你可以嘗試用一個最小業務用例去實作并了解,比如,修改客戶名稱業務用例,你可以先把這個業務用例中所涉及的概念抽離出來,比如客戶、客戶名稱,修改客戶名稱,需要首先找到這個具體的客戶,當然,可能會有一些限制操作,但不管怎樣,“修改客戶名稱”這個業務所表達的結果,就是這個客戶的名稱要被修改,是以你要實作修改客戶名稱這個操作,可能實作的代碼就是下面這樣:

上面的實作代碼和之前的 saveCustomer 代碼,很明顯的差別,首先,你修改客戶名稱,如果你使用的是 saveCustomer,你需要在方法參數中,傳遞一大堆的 null,而且整個方法内部充滿了一些沒必要的操作,而且你把 saveCustomer 方法描述給領域專家聽,我想他們肯定也會不知所雲,相反,changeCustomerPersonalName 就是通用語言的一種表達方式。

一個業務用例,從一開始的讨論,到最後的實作,整個過程中所涉及的方方面面,其實都可以了解為通用語言的表現,關于通用語言的界定問題,作者還提到幾點:

通用語言在團隊範圍内使用,并且隻表達一個單一的領域模型。

隻有當團隊工作在一個獨立的限界上下文中時,通用語言才是“通用”的。

“通用語言”并不表示全企業、全公司或者全球性的萬能的領域語言。

每個限界上下文都有自己的通用語言,而有時語言間的術語可能有重疊的地方。

。。。

以上幾點警示你,通用語言有一定的界定,并不是所有團隊,也不是一個項目,而是一個單一的領域模型或者一個獨立的界定上下文,你可以把它了解為一個領域模型或者一個獨立界定上下文的具體表現,或者稱之為過程展現。

以上隻是簡單的概念整理,并沒有一些實際意義,具體的體會隻能在實踐中更加深刻,就記錄到這!

本文轉自田園裡的蟋蟀部落格園部落格,原文連結:http://www.cnblogs.com/xishuai/p/iddd-anemia-model-ubiquitous-language.html,如需轉載請自行聯系原作者

繼續閱讀