1. 程式人生 > >領域驅動設計之我見-實現模式

領域驅動設計之我見-實現模式

領域驅動設計,這個名詞從字面上來看分為三個部分,領域,設計,驅動。前面我針對領域做了一些敘述,總結起來就是一句話:技術專家和業務專家一起採用面向物件的思想來提取業務模型。那麼接下來要看設計了,有了比較恰當的業務模型了,怎麼將其設計為一個合理的軟體系統呢?

在網上搜一下,隨處可見各大博主對領域驅動設計的精彩描述,但是有讀過埃文斯前輩的《領域驅動設計:軟體核心複雜性應對之道》的讀者會發現,各大博文基本上都是對該書中【第二部分 模型驅動設計的構造塊】一章的概述。當然筆者也喜歡隨大流,首先介紹一下模型驅動設計的構造塊。埃文斯前輩設計中將整個系統分層為介面層(UserInterface),應用層(Application)、領域層(Domain)、基礎設施層(Infrastructure),如下圖所示

如上設計,最上層為使用者介面層,主要負責系統入口協議等,該層不處理任何業務邏輯,只負責呼叫接入(入口協議),應用轉發(呼叫Application層),如上圖的設計中允許UserInface層跨層呼叫Infrastructure層,而現實的設計中我們不建議這樣。

Application是應用層,和以往事務指令碼的Service是截然不同的東西,該層中並不做詳細的業務邏輯的封裝,而是建立Domian並呼叫Domain中的相應方法完成業務邏輯處理,並呼叫Infrastructure完成Domain的持久化操作,該層需要負責事務的控制,保證資料的一致性。

Domain層的東西是核心,核心的業務邏輯應該以Domain為物件進行分類封裝,Domain的劃分以業務為基準(前面章節已經做了詳解),Domain層在技術層面的建模通用技巧在下面會做詳細介紹,該層只能向下呼叫Infrastructure完成自身模型的初始化和持久化。

Infrastructure層,該層類似於以往事務指令碼的Dao層,然而在概念上,Infrastructure卻是另外一個東西。以往的面向事務指令碼的程式設計中,以資料表為主,所有的事務指令碼直指目的就是完成表的CURD,而DDD中以模型為核心,Infrastructure層是為了重建已有的Domain,並在退出時持久化記憶體中的Domain物件。Infrastructure層不僅包含對關係資料庫的處理,還包括對分散式快取處理、對外系統的接入(integration)以及分散式訊息佇列的push操作。總而言之,就如infrastructure的直譯“基礎設施”一樣,infrastructure

層就像應用系統之下的基礎設施一樣,對應用提供資料的供給(模型重建/初始化模型)和資料的長久保持。

完成了DDD的總體架構設計之後,再來看看詳細的domain模型設計。埃文斯前輩將模型分為值(ValueObject)、實體(Entity)、服務(Service)、模型工廠(Factory)、資料倉庫(Repository),如下圖所示

Entity/ValueObject

首先,我們來看一下什麼叫做ValueObject,這個需要和Entity對照著來說,兩者在程式碼中皆表示為一個類(物件),從業務上來講也分別表示一個業務實體。那麼什麼樣的實體應該定義為一個ValueObject或者一個Entity呢?從字面上來看,ValueObject就是一個值物件,而Entity是實體。實際上在DDD中也就是這意思,Entity是一個完整的具有生命週期的可以通過唯一標識來識別東西,而ValueObject則是Entity的一類屬性。

例如,會員可以認為是一個Entity,一個會員就是具有生命週期的東西,具有唯一的標識可以將A使用者和B使用者分開(唯一標識:賬戶號),而這個使用者的地址(例如:南京市/玄武區/徐莊軟體園)就應該定義為一個ValueObject物件,當我們定義好一個地址,它既可以是A使用者的地址也可以是B使用者地址,當我需要更新A使用者地址時,可以直接銷燬A用關聯的地址物件然後重新建立一個地址物件關聯到A使用者。

Service

既然我們已經將業務邏輯都封裝在Entity和ValueObject中了,為什麼還需要一個Service呢?事實上,當我們建模之後發現有些業務既不單單屬於A領域物件,又不單單屬於B領域物件,我們可以提取出一個單獨的服務來完成此項業務。例如當我們把金融會員業務劃分為UserBase、PaymentPassword、LoginPassword、Auth和Lable後發現使用者註冊的業務邏輯同時跨越UserBase和LoginPassword兩個領域模組,此時把註冊的業務分裝在任何一個領域中都是不合適的,如果在各個領域中同時新增regist方法也不合適,那麼我們可以抽象出一個register的Service,方法中處理使用者基礎資訊的校驗,並呼叫LoginPassword的加解密方法進行帳密處理。

Service和以往事務指令碼的Service一樣嗎?只能說非常像,唯一不同的是,以往事務指令碼的Service是自己全權負責業務邏輯,而此處的Service不僅編寫業務邏輯,還會呼叫Entity和 ValueObject的方法來協調Entity和ValueObject,從而避免Entity和ValueObject的耦合。

Factory

領域工廠,這個沒什麼特殊的,在以往的程式設計經驗中我們多處使用過工廠模式,並在工廠模式中獲得過好處,讓物件的建立更加簡潔,當然工廠並非必須命名一個以Factory結尾的類,才叫工廠。物件的建構函式本身就是一個工廠方法,一個Entity物件內部能夠構造出另外一個Entity物件的方法也是工廠方法,例如UserBase物件中的createDefaultUserHeadImg,使用者註冊時通過該方法可以給使用者建立一個隨機的系統頭像UserHeadImg,該方法就是一個工廠方法。

Repository

資料倉庫,主要完成領域物件的重建以及物件的持久化操作。對於一個從DDD著手設計良好的資料庫系統,該Repository的設計主要就是呼叫infrastructure層來完成表的CURD、快取的操作以及外系統介面的呼叫。然而如果我們希望將一個老舊的基於事務指令碼開發的系統,重構為基於DDD設計的系統,那麼Repository的設計會是一個挑戰。

以上對領域驅動設計的各個構造快做了一個簡速,這些只是軟體設計中的一個通用的套路,如前章講過,對於一個面向物件建模大師,不需要任何套路也能做好DDD開發設計,但是對於我等二流的工程師,最好還是乖乖的跟隨埃文斯前輩的步伐,讓系統整齊劃一。

原文連結