DDD與Repository

bluesky234發表於2020-08-19

Repository已經不是什麼新鮮概念了。DDD模型自2004年提出,發展至今已經16年了。但是不少企業卻無法實施,其原因也很簡單:DDD是基於需求的,而很多並不理解需求;DDD是容易實現的,而很多設計者並不會程式設計。這種情況就有一些兩頭不討好,而如果有辦法結合統一的話,則會非常好用。

 

學習Repository的過程中,最先要進行的是思想的轉變。在過往的程式設計過程中,大家往往將目光聚焦在CRUD中,導致每個程式設計師首先想的是我如何使用SQL實現我所要的目標。而在DDD過程中,實踐者應當將目光聚焦中功能上,首先將需求分解為若干個功能,然後再將功能進行組合。

 

舉例而言,某系統擁有組織機構和使用者功能。組織機構樹的每個部門下屬若干個職位,每個職位都有使用者擔任,每個使用者可以出任多個部門的多個職位。這時,系統設計師告訴你這裡要有以下功能:

部門管理(部門的CRUD,部門下職位的CRUD,職位與部門的CRUD)

使用者管理(使用者的CRUD)

講到這裡,很容易看出,這裡其實有四張表,也就是部門,職位,使用者和使用者職位關聯表。表關係也容易理出,部門與職位1對多,職位與使用者多對多。

 

如果你使用的是DAO思想(或者說分層思想),那麼需求分析做到這一步也就結束了。你可以直接通過上述內容整理出你需要實現的介面,即每張表的CRUD。然後在前端實現一個介面,每個介面呼叫相關的介面程式也就寫完了。比如,其中一個介面可能是這樣的:

[Route(“~/api/SetPosition”)]

public void SetPosition(Guid userId, Guid positionId);

 

那麼,現在問題來了。需求發生了一個變更,來了一個全新的需求,客戶說我現在需求每個部門的更改必須通過流程進行。即當部門資訊發生變更時,必須層層稽核,最後才通過後,才能在更新資料。這個稽核過程甚至包含了一部分關鍵職位的人員變化。

這時,那個坑人的系統設計師又站出來了,給了你一系列功能變化表:

 

部門修改申請(部門修改申請CRUD,部門申請稽核,部門申請同步到部門表,部門申請同步到職位表)

 

看上去,這個設計很美好“自頂向下逐步細化”分解的也非常舒服。但是,你仔細研究一下就發現這裡有兩個巨大的坑:

1、新建部門修改申請。在部門修改申請時,試問是否要將以前的部門資料複製到這張申請表中?如果你不復制,那了不得了,全部門所有手動資料全部要使用者自行輸入此表,那恐怕終端使用者會和你鬧的不可開交——這什麼垃圾軟體?!而如果你打算實現他,那我告訴你,這張可怕的部門表裡,欄位不多,100個(呵呵)。

2、如果你說功能1其實是必須實現的新功能,和設計關係不大。那麼你再觀察,將部門申請同步到部門這個功能。他絕對可以細分為“修改部門”和“修改職位”兩個子功能,而這兩個子功能其實是之前的介面就實現的。那麼,你是否為之前的介面留下了複用性?仔細看看之前介面的實現程式碼,你就會悲劇的發現,70%的可能性那個介面是無法複用的,因為查詢程式碼其實不太一樣。

 

那這只是我隨手說的一個需求變更,如果有更多的需求變化呢?那麼雖然程式碼還是能夠複用一部分,設計空間釋放也不會太麻煩。但是,仔細評判你的程式碼和設計,就會發現原來優雅而簡潔的可複用設計的複用性越來越低,原來整齊而易讀的程式碼的可讀性越來越差。這就是人間悲劇。

 

而這時,Repository的思想從天而降,他也許能夠為你可憐的程式碼帶來一些讓你驚喜的變更。如果使用DDD的思想設計上述內容,首先你需要確定領域。顯而易見的,這裡的領域可以這樣劃分:

 

使用者領域:新增使用者,刪除使用者,修改使用者,修改使用者的職位,移除使用者的職位

部門領域:新增部門,刪除部門,修改部門,查詢部門下的職位,查詢部門下的使用者

職位領域:新增職位,修改職位,刪除職位,查詢職位下的使用者,將使用者新增到職位中,將使用者從職位中移除

注:這裡,如果是我寫程式碼,我很可能會把“部門領域”和“職位領域”合併。這個並無不可,因為兩者其實沒有那麼明顯的邊界。

 

在這個設計中,可以看到其實有些功能是重複的,比如說“修改使用者的職位”和“將使用者新增到職位中”。但是,在領域設計中,我卻將其認為是兩個不同的功能,因為他們的主體不一樣。對前者而言,我先查出使用者,函式的引數是“使用者ID”和“職位名稱”,這裡使用出字串的職位名稱,即意味著對於使用者領域來說,他不需要認識“職位”這個類。對於後者而言,我先查出職位,函式引數是“職位”和“一個或者多個使用者ID”。這意味著,對於職位領域來說,他也不需要認識使用者這個類。

這裡可以看到,領域之間,耦合度很低。其實達到了最小知識原則所要求的內容。但是,實現過程中,可能會有這樣的疑問,將職位新增到使用者過程中,難道你不需要判斷使用者是否存在嗎?當然,判斷還是要判斷的,但是我完全可以不認識使用者這個類。通過將“使用者職位關係表”中的“使用者ID”欄位與使用者表中的“ID”欄位做出外來鍵關係,完全可以讓資料幫我保證資料有效性。我只需要做一個簡單的異常處理即可。

另外,耦合度低不等於不能耦合,在這裡查詢一次使用者表,我認為也沒有突破什麼界限,所以完全沒有問題。

 

在設計完領域後,需要再設計邊界,也就是說由哪些類將這些功能全部暴露給外界。這時可以這麼設計:

 

部門類:新增職位,修改職位,刪除職位,查詢職位下的使用者,將使用者新增到職位中,將使用者從職位中移除

使用者類:查詢我所在的部門和職位

使用者服務類:使用者的CRUD

部門服務類:部門的CRUD

 

這裡,實際是將部門當作了職位的聚合。這只是我隨手寫的設計,沒有實踐過也不知道有沒有什麼問題。但我想大致應當是正確的。這時,我就將所有功能都通過這幾個類暴露在外界。在考慮這些內容的情況下,再來上述需求時,問題就明確了,他需要新建一個領域:部門修改申請。

部門修改申請:通過部門新建修改申請,通過舊的修改申請新建修改申請,稽核修改申請,將修改申請同步到部門中,將修改申請同步到職位中。

 

現在再來看之前的兩個大坑。問題1其實是規避不了。因為這個就是新功能,規避的唯一辦法就是加錢,錢到位了功能也就到位了。而問題2確實就簡單了,因為你可以直接呼叫暴露在“修改職位功能”將申請表中的使用者給到對應職位,也可以通過呼叫“修改部門功能”直接將部門資訊反向同步,而不需要考慮程式碼是否優雅,因為這裡就是呼叫一個函式,並不存在優雅與否的問題。

 

再到以後,如果再有新功能,哪怕你還是需要釋放設計空間。但你在重構的時候,已經整理過的功能就不需要整理第二遍。你只需要交被釋放出的設計空間全部放回領域中,重構的工作量大大減少。而這,就是我所看重的DDD的核心優勢。

針對到實現層面,之前那些亂七八糟的領域功能,其實就是Repository,他的出現自然而又簡單。你所需要的只是簡單的變化一下自己的思想,多寫幾十行程式碼,僅此而已。

 

最後,稍稍總結一下。完成以上內容的核心和關鍵其實並不是你對DDD瞭解多少。而真正有效的是你對需求瞭解多少,你認為需求有多少內容可能發生變化。對需求把握才是軟體設計的核心。任何設計思想,設計模式都基於對需求的理解。我個人對軟體思想的重要理解:

不基於需求任何想法都空談,不理解需求任何程式碼都是胡說,不把握變化任何設計都是假想。

與君共勉。

相關文章