淺談“領域驅動設計”

Eric Evans所著的《領域驅動設計》(Domain-Driven Design:通常簡稱爲“DDD”)一書可以說是經典中的經典,雖然“領域”的概念早就存在,但是直到這本書的出現,才讓人們真正開始認真審視軟件的構建,相信你看了這本書後會真正體會領域的力量,也正是這個力量決定了軟件最終的價值。

領域的含義:

簡單的說,每個軟件程序都會與其用戶的活動或興趣相關,其中使用程序的主要環境稱爲軟件的“領域”。

領域中形形色色的業務邏輯構成了軟件豐富多采的行爲。舉例來說,銀行財務系統中,領域邏輯就包括了諸如開戶,轉帳等等操作。可能你會說,PHP程序員很少會接觸銀行系統,這樣的例子不夠淺顯,那我舉一個更常見的例子,大凡程序員應該都接觸過文章管理系統,它裏面的置頂,加精等操作就是領域邏輯。這樣看來,似乎用例對應的動作都是領域邏輯了,但是答案是否定了,比如說,文章管理系統中保存文章往往就不是領域邏輯,因爲它只是一個和持久化相關的動作而已,是純粹的技術實現,但是銀行財務系統中的保存現金通常卻被劃爲領域邏輯,因爲它就是我們常說的存款,有明確的業務含義。看到這,似乎大家又有些Faint了,這裏給出一個判斷是否是領域邏輯的原則:就是這個邏輯動作是否有明確的業務上的含義,或者說是否是業務相關的,而不僅僅是技術相關的。

只有將技術實現手段從領域問題中剝離才能保證領域本身的精煉,保證程序員可以把精力集中到領域問題本身上來,而不會滿腦子都是技術實現手段。

領域的組成:

按照Eric的表述,通常將領域中的組成角色分爲以下五種:

實體(Entity):擁有唯一標識的對象。
值對象(Value Object):沒有唯一標識的對象。
工廠(Factory):定義創建實體的方法。
倉儲(Repository):管理實體的集合並封裝其持久化過程。
服務(Service):實現不能指派或封裝在一個單一對象上的操作。

領域的思考:

下面針對上面介紹的五種領域角色來逐一討論。

實體的概念是比較好理解的,這樣的例子很多,比如說每一個人都可以看作是一個“與衆不同”的實體,我之所以用與衆不同這個詞是爲了強調實體必須是能夠唯一標識出來的,即便是在我們看作長得一模一樣的雙胞胎,他們也是能更根據一些標識來區分開,比如指紋,可能你會擡槓,要是沒有手的殘疾人怎麼辦?那樣我們還可以使用DNA檢測,當然,這些都是笑談了,實際編程的時候,一般是使用一個自增數來作爲標識,比如在MySQL數據庫中保存實體的時候可以使用anto_increment屬性的自增字段。需要注意的是如果想判斷兩個實體是否相等,不能根據實體的屬性來判斷,必須絕對依賴實體的標識,十年前的你和現在的你雖然在身高,體重,年齡等衆多重要的屬性中多或多或少的發生了變化,但你還是你,因爲你的DNA不會因爲這些屬性的變化而變化。這些理解起來似乎有些哲學的味道了。

值對象的含義,老實說相對實體來說比較模糊,很多人喜歡把數據傳輸對象也稱爲值對象(數據傳輸對象和我們這裏說的值對象是有差別的)讓人們對值對象的理解產生過很多歧義,而且值對象的例子不如實體那麼直接。從字面上來理解,值對象沒有唯一標識,大多數情況下,值對象是不變的,所以系統不用實時的跟蹤他們,用的時候就實例化一個,不用的時候就銷燬,但是,什麼時候使用值對象?把哪些屬性劃爲值對象?值對象的作用是什麼?這些都是值得考慮的問題。通常來說,當我們進行領域建模的時候,優先把唯一標識和經常用來檢索對象的信息作爲實體的屬性,而其他信息根據相關性或者劃分到其他實體中,或者劃分爲值對象,舉例來說:一個CMS系統中,對於文章實體而言,文章編號,文章標題等都應該作爲文章實體的屬性存在,而對於文章有效性期限的開始時間,結束時間兩個信息則應該被放在一個獨立的值對象中,這其中,只有開始時間或結束時間,或者開始時間和結束時間同時都存在或不存在,會代表不同的邏輯意義,合理使用值對象,既有利於屏蔽一些相關邏輯的複雜性,也可以保持實體對象的簡潔。

工廠相對與前兩者會好理解的多,畢竟從名字上就能體現出它的職責,那就是創建對象。既然是創建對象,那我們直接實例化一個不行麼?簡單的情況是可以的,但是工廠往往會帶來巨大的好處,簡單的說就是屏蔽了創建對象的複雜性。領域創建對象強調的關聯,一組相關的對象應該被看作一個整體,對於其中任何對象的訪問也應該從這個整體的“根”開始(通常整體中最重要的實體作爲根),所以複雜的關聯必然會使創建過程同樣複雜起來,那我們可不可以在“根”實體的構造函數中完成對象的組裝呢,簡單的情況可以,複雜的不合適,比如說組裝汽車,通常是在工廠裏由組裝工人和機器人來操作完成,如果我們在“根”的構造函數裏完成組裝,無異與在汽車裏配備了組裝工人和機器人,這當然是不必要的,汽車一旦組裝出廠,就不需要組裝工人和機器人了,此時再附帶他們是一種累贅。

倉儲的概念和一些人常說的數據訪問對象(DAO)有些類似,但是並不等同,二者一個很大的不同是倉儲有“根”的概念,而數據訪問對象往往是按照數據庫的表來劃分的。使用倉儲主要是爲了查詢和持久化領域對象,而領域對象之間往往會有複雜的聚合關係,爲了保證不變量,所以才引入根的概念,對領域對象中某個子對象的訪問必須通過根來導航。這樣說可能不易理解,我舉一個簡單的例子:轎車,輪胎可以看成是一個領域對象的聚合,轎車是這個聚合的根,如果我們想訪問輪胎,必須通過轎車的導航來進行,爲什麼如此規定,因爲轎車和輪胎之間存在一個不變量:一個轎車有四個輪胎,如果允許客戶端直接訪問輪胎,那麼就很難保證此邏輯不被破壞。

服務這個名詞被用過很多次了,但是以前人們說的服務大多是從技術角度而言的,從分層來看屬於應用層。一般是諸如註冊成功發送一個郵件之類的東西,領域驅動設計中的服務不是這個範疇的概念,它強調的是實體之間的相互關係,而不是純粹意義上的技術手段。舉一個例子來說:CMS系統裏,如果一篇文章被加入精華,則文章作者的經驗值加一。此邏輯中涉及量個實體:文章實體和作者實體。經驗值加一的邏輯不管是建立在文章實體裏,還是作者實體裏都顯得冗餘,所以有必要在實體之上在抽象出一個服務層來處理。這裏可能有人會問:這樣的邏輯我們放到傳統意義上的應用層不行麼?那樣做不能說不行,但是多數情況不好,因爲此邏輯屬於領域邏輯,而不是應用邏輯,如果放在應用層,領域邏輯就外泄了,領域層也就成爲了擺設,但是也有例外,有時候我們可能一時很難分辨一個邏輯是領域邏輯還是應用邏輯,這個時候把此邏輯加入到應用層是沒有問題的,如果以後發現其作爲領域邏輯更合適的話再重構不遲。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章