天天看點

DDD 領域驅動設計-如何完善 Domain Model(領域模型)?

閱讀目錄:

JsPermissionApply 生命周期

改進 JsPermissionApply 實體

重命名 UserAuthenticationService

改進 JsPermissionApplyRepository

改進領域單元測試

如何完善領域模型?指的是完善 JS 權限申請領域模型,也就是 JsPermissionApply Domain Model。

在上篇博文中,關于 JsPermissionApply 領域模型的設計,隻是一個剛出生的“嬰兒”,還不是很成熟,并且很多細緻的業務并沒有考慮到,本篇将對 JsPermissionApply 領域模型進行完善,如何完善呢?望着解決方案中的項目和代碼,好像又束手無策,這時候如果沒有一點思考,而是直接編寫代碼,到最後你會發現 DDD 又變成了腳本式開發,是以,我們在做領域模型開發的時候,需要一個切入點,把更多的精力放在業務上,而不是實作的代碼上,那這個切入點是什麼呢?沒錯,就是上篇博文中的“業務流程圖”,又簡單完善了下:

DDD 領域驅動設計-如何完善 Domain Model(領域模型)?

在完善 JsPermissionApply 領域模型之前,我們需要先探讨下 JsPermissionApply 實體的生命周期,這個在接下來完善的時候會非常重要,能影響 JsPermissionApply 實體生命周期的唯一因素,就是改變其自身的狀态,從上面的業務流程圖中,我們就可以看到改變狀态的地方:“申請狀态為待稽核”、“申請狀态改為通過”、“申請狀态改為未通過”、“申請狀态改為鎖定”,能改變實體狀态的行為都是業務行為,這個在領域模型設計的時候,要重點關注。

使用者申請 JS 權限的最終目的是開通 JS 權限,對于 JsPermissionApply 實體而言,就是自身狀态為“通過”,是以,我們可以認為,當 JsPermissionApply 實體狀态為“通過”的時候,那麼 JsPermissionApply 實體的生命周期就結束了,JsPermissionApply 生命周期開始的時候,就是建立 JsPermissionApply 實體對象的時候,也就是實體狀态為“待稽核”的時候。

好,上面的分析聽起來很有道理,感覺應該沒什麼問題,但在實作 JsPermissionApplyRepository 的時候,就會發現有很多問題(後面會說到),JsPermissionApply 的關鍵字是 Apply(申請),對于一個申請來說,生命周期的結束就是其經過了稽核,不論是通過還是不通過,鎖定還是不鎖定,這個申請的生命周期就結束了,再次申請就是另一個 JsPermissionApply 實體對象了,對于實體生命周期有效期内,其實體必須是唯一性的。

導緻上面兩種分析的不同,主要是關注點不同,第一種以使用者為中心,第二種以申請為中心,以使用者為中心的分析方式,在我們平常的開發過程中會經常遇到,因為我們開發的系統基本上都是給人用的,是以很多業務都是圍繞使用者進行展開,好像沒有什麼不對,但如果這樣進行分析設計,那麼每個系統的核心域都是使用者了,領域模型也變成了使用者領域模型,是以,我們在分析業務系統的時候,最好進行細分,并把使用者的因素隔離開,最後把核心和非核心進行區分開。

先看下之前 JsPermissionApply 實體的部分代碼:

Process 的設計會讓領域專家看不懂,為什麼?看下對應的單元測試:

Process 是啥?如果領域專家不是開發人員,通過一個申請,他會認為應該有一個直接通過申請的操作,而不是調用一個不知道幹啥的 Process 方法,然後再傳幾個不知道的參數,在 IDDD 書中,代碼也是和領域專家交流的通用語言之一,是以,開發人員編寫的代碼需要讓領域專家看懂,至少代碼要表達一個最直接的業務操作。

是以,對于申請的處理,通過就是通過,不通過就是不通過,要用代碼表達的簡單粗暴。

改進代碼:

這樣改進還有一個好處,就是改變 JsPermissionApply 狀态會變的更加明了,也更加受保護,什麼意思?比如之前的 Process 的方法,我們可以通過參數任意改變 JsPermissionApply 的狀态,這是不被允許的,現在我們隻能通過三個操作改變對應的三種狀态。

JsPermissionApply 實體改變了,對應的單元測試也要進行更新(後面講到)。

UserAuthenticationService 是領域服務,一看到這個命名,會認為這是關于使用者驗證的服務,我們再看上面的流程圖,會發現有一個“驗證使用者資訊”操作,但前面還有一個“驗證申請狀态”操作,而在之前的設計實作中,這兩個操作都是放在 UserAuthenticationService 中的,如下:

IsHasBlog 屬于使用者驗證,但下面的 jsPermissionApply.Status 驗證就不屬于了,放在 UserAuthenticationService 中也不合适,我的想法是把這部分驗證獨立出來,用 ApplyAuthenticationService 領域服務實作,後來仔細一想,似乎和上面實體生命周期遇到的問題有些類似,誤把使用者當作核心考慮了,在 JS 權限申請和稽核系統中,對于使用者的驗證,其實就是對申請的驗證,所驗證的最終目的是:某個使用者是否符合要求進行申請操作?

是以,對于申請相關的驗證操作,應該命名為 ApplyAuthenticationService,并且驗證代碼都放在其中。

除了 UserAuthenticationService 重命名為 ApplyAuthenticationService,還增加了對 JsPermissionApply 狀态為 Lock 的驗證,并且 IJsPermissionApplyRepository 的 GetByUserId 調用改為了 GetEffective,這個下面會講到。

原先的 IJsPermissionApplyRepository 設計:

這樣的 IJsPermissionApplyRepository 的設計,看似沒什麼問題,并且問題也不出現在實作,而是出現在調用的時候,GetByUserId 會在兩個地方調用:

ApplyAuthenticationService.Verfiy 調用:擷取 JsPermissionApply 實體對象,用于狀态的驗證,判斷是否符合申請的要求。

領域的單元測試代碼中(或者應用層):擷取 JsPermissionApply 實體對象,用于更新其狀态。

對于上面兩個調用方來說,GetByUserId 太模糊了,甚至不知道調用的是什麼東西?并且這兩個地方的調用,擷取的 JsPermissionApply 實體對象也并不相同,嚴格來說,應該是不同狀态下的 JsPermissionApply 實體對象,我們仔細分析下:

ApplyAuthenticationService.Verfiy 調用:判斷是否符合申請的要求。什麼情況下會符合申請要求呢?就是當狀态為“未通過”的時候,對于申請驗證來說,可以稱之為“有效的”申請,相反,擷取用于申請驗證的 JsPermissionApply 實體對象,應該稱為“無效的”,調用命名為 GetInvalid。

領域的單元測試代碼中(或者應用層):用于更新 JsPermissionApply 實體狀态。什麼狀态下的 JsPermissionApply 實體,可以更新其狀态呢?答案就是狀态為“待稽核”,是以這個調用應該擷取狀态為“待稽核”的 JsPermissionApply 實體對象,調用命名為 GetWaiting。

原先的單元測試代碼:

看起來似乎沒什麼問題,一個申請和一個稽核測試,但我們仔細看上面的業務流程圖,會發現這個測試代碼并不能完全覆寫所有的業務,并且這個測試代碼也有些太敷衍了,在測試驅動開發中,測試代碼就是所有的業務表達,它應該是項目中最全面和最精細的代碼,在領域驅動設計中,當領域層的代碼完成後,領域專家檢視的時候,不會看領域層,而是直接看單元測試中的代碼,因為領域專家不懂代碼,并且他也不懂你是如何實作的,它關心的是我該如何使用它?我想要的業務操作,你有沒有完全實作?單元測試就是最好的展現。

我們該如何改進呢?還是回歸到上面的業務流程圖,并從中歸納出領域專家想要的幾個操作:

填寫 JS 權限申請(需要填寫申請理由)

通過 JS 權限申請

拒絕 JS 權限申請(需要填寫拒絕原因)

鎖定 JS 權限申請

删除(待考慮)

上面這幾個操作,都必須在單元測試代碼中有所展現,并且盡量讓測試顆粒化,比如一個驗證操作,你可以對不同的參數編寫不同的單元測試代碼。

改進好了代碼之後,對于開發人員來說,任務似乎完成了,但對于領域專家來說,僅僅是個開始,因為他必須要通過提供的四個操作,來驗證各種情況下的業務操作是否正确,我們來歸納下:

申請 -> 申請:ApplyTest -> ApplyTest

申請 -> 通過:ApplyTest -> ProcessApply_WithPassTest

申請 -> 拒絕:ApplyTest -> ProcessApply_WithDenyTest

申請 -> 鎖定:ApplyTest -> ProcessApply_WithLockTest

申請 -> 通過 -> 申請:ApplyTest -> ProcessApply_WithPassTest -> ApplyTest

申請 -> 拒絕 -> 申請:ApplyTest -> ProcessApply_WithDenyTest -> ApplyTest

申請 -> 鎖定 -> 申請:ApplyTest -> ProcessApply_WithLockTest -> ApplyTest

确認上面的所有測試都通過之後,就說明 JsPermissionApply 領域模型設計的還算可以。

DDD 傾向于“測試先行,逐漸改進”的設計思路。測試代碼本身便是通用語言在程式中的表達,在開發人員的幫助下,領域專家可以閱讀測試代碼來檢驗領域對象是否滿足業務需求。

當領域層的代碼基本完成之後,就可以在地基上添磚加瓦了,後面的實作都是工作流程的實作,沒有任何業務的包含,比如上面對領域層的單元測試,其實就是應用層的實作,在添磚加瓦的過程中,切記地基的重要性,否則即使蓋再高的摩天大樓,地基不穩,也照樣垮塌。

實際項目的 DDD 應用很有挑戰,也會很有意思。

無意間發現了 Visual Studio 2015 Update 2 一個很實用的功能:

DDD 領域驅動設計-如何完善 Domain Model(領域模型)?

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

繼續閱讀