前面細講了基于CQS的4層架構,其中的領域模型層也就是六邊型架構中的內核在整個開發流程中作業占比最大,也是需要工程師最需要關注地方,那么話說回來了,里面到底包含了什么東西需要投入如此高的關注度,答案還用說?必然是領域模型啊,比如物體、值型別、業務服務等,您別忘了咱們講的是領域驅動設計,具體可參看如下圖所示的領域模型層(后續簡稱BO層)中的元素,這里面東西較多,基乎每一種都可以開一章來講,也就是可以水好多的文字,

一、業務模型層中的特色元素
1、業務例外
BO層中元素比較多,但這里面最具特色應該是“業務例外”,您說把領域模型、領域服務歸結為BO中的元素這本是無可厚非的,因為它們本來面向的就是業務物體、業務邏輯,把例外也歸結為BO中的元素是幾個意思呢?而且,書上都沒這么講過,我這是不是故意的嘩眾取寵騙流量?這話不能這么說,書上沒講過的東西多著呢,人家寫書的時候都會站在一定的前提之上,比如讀者應該會一門開發語言,應該做過實際的專案,應該有具備哪些基本知識等,咱這種博客什么樣的受眾都有,內容本身也是個人經驗的總結,有點特殊的東西很正常,而且我不僅要講書上沒有的東西,還會講得很細,您就踏實的看吧,
回到業務例外這個事情上來,在軟體的分析設計程序中,那些有形的物體(一般是名詞)可以建成物體,比如訂單、賬戶、商品等;還有一些是無形的但在需求程序中也隱晦的提到了,最典型的就是“事件”,這是對動詞進行建模的經典案例,而例外則是典型的、經常被隱晦提及的需要被建模的物體,由于其隱蔽性所以在建模時很容易被忽略,舉個例子,需求中可能會這樣描述:下單失敗時應該告知用戶失敗的原因,這個場景中提及了“失敗原因”這個名詞,那應該用什么東西來描述它呢?這時就需要業務例外來發揮熱量了,顯性的提及失敗的處理邏輯還好一些,還有一種常見的需求形式比如:下單成功后,給用戶發送短信通知,這種需求只描述了正向的業務而沒有說明如果失敗了要如何處理,此時就需要工程師發揮自己的主動性,結合業務的使用形式為失敗的場景建立適宜的處理方式,當然也可以和客戶或產品經理就此等情況進行協商,實際的參與到需求完善的程序中,
此種作業方式也應對了我在前面所說過的:軟體開發并不是無腦的堆代碼,實際上,我也見過一些工程師,當面臨業務BUG時候很不愿意承認自己的過失,而是將責任推給需求或其它人,常用的口頭語就是“你也沒說啊?”,對方的常見回答是“你為什么不問”?然后就開始撕了……客觀來講,我個人比較不喜歡這種工程師,缺少必要的積極性和主動性,作業其實首先成全的是自己,能否讓自身成長也只能靠自己,畢竟“師傅領進門,修行在個人”;滿足公司的需要這本來就是義務,畢竟你從公司拿錢了;另一方面也需要考慮如何進行自我提升,有時候軟技能比會什么什么技術更加重要,我在以往的作業中也曾經歷了許多的故事,有些是個人的不足,有些是公司的政治,但一直在努力的進行自我提升包括寫這些文章這個事情的本身,不順是眼前的,有些時候并不是您自身的問題,但能否做一些事情為將來開辟出一條適合自己的路,這是可控的,
有關業務例外還有一些補充,您在日常用的時候務必給他一個見名之意的名字,這么說吧,給例外一個好名兒比您費半天勁想詞去描述例外原因更有價值,名起得好在拋例外的時候甚至不用寫具體原因,另外,實踐中最好讓業務例外繼承于“Exception”而不是“RuntimeException”,也就是使用檢查例外以避免例外未被正確捕獲,我寫了一個業務例外的示例供參考,注意名字啊,我個人覺得起得挺牛掰的,看名就知道什么錯誤,另外一點,如果把代碼再寫細一點,做一個業務例外的基類并讓所有的具體例外從它繼承,就可以使用一些全域例外處理機制,比在每個方法里做try...catch要強得多,
public class DeploymentApprovalFormCreationException extends Exception { public DeploymentApprovalFormCreationException() { super(); } public DeploymentApprovalFormCreationException(String message) { super(message); } public DeploymentApprovalFormCreationException(String message, Throwable cause) { super(message, cause); } public DeploymentApprovalFormCreationException(Throwable cause) { super(cause); } protected DeploymentApprovalFormCreationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } }
2、資源倉庫介面
除業務例外另外需要著重說明的是“資源倉庫介面”,有些工程師會把資源倉庫當做DAO來用,這個其實是一種誤用,資源倉庫要包含哪些介面和您的業務是強關聯的,再說直白一點就是由業務來決定資源倉庫有哪些能力,兩者的目的有著本質的不同:DAO用于操作資料模型,資源倉庫用于操作領域模型,系統在實作的時候,領域模型最侄訓需要變成資料模型才能進行存盤和索引,這也是資源倉庫該干的事情,
具體來看,領域模型一般會有大量的嵌套物件,各物件間關系復雜,而且也不是所有的物件都需要進行持久化;資料模型相對簡單得多,常用的關系型資料庫對應的模型也不過是一種二維關系,想要實作兩者的轉換已經不僅僅是引入一個簡單的設計模式比如工廠就能解決的了,還需要再應用一種更為復雜的設計來實作領域模型和資料模型的解耦合,引入“資源倉庫”可以達到這個目的;另外,資源倉庫還能約束您在設計的時候要以業務模型為驅動以避免陷入面向資料庫設計的情況,為了實作洋蔥架構的效果,設計時還需要把資源倉庫的定義與實作進行分離,由于定義一般是以介面的形式,所以并不會為BO層引入更多的針對基礎設施的依賴,不得不感嘆DDD的那些先驅還真是聰明,
|
重點! 進行領域模型設計時,需要首先考慮領域模型的實作再決策存盤方式,資源倉庫的引入可以起到三個用:1)解決領域模型與資料模型間的異構問題,達到轉換器的作用;2)提供對領域模型的序列化和反序列化能力的支撐;3)約束您在考慮問題的時候應該使用業務驅動的方式, |
這里有一個問題,為什么說需要根據業務的需求來設計資源倉庫呢?舉一個例子,在我們常見的電商購物業務中有“下單”概念,下單的一個重要步驟是對訂單模型進行存盤,也就順澩倉庫應當具備訂單持久化的介面;支付后把訂單的狀態變成已支付,說明還需要有一個從存盤中查詢訂單的能力和變更訂單資訊的能力,分別對應查詢和編輯;訂單一般不能洗掉只能作廢說明不需要有洗掉的需求,針對上述需求,我們發現訂單資源倉庫應當只有三個介面:1)查詢單個訂單;2)更新訂單;3)存盤新訂單,具體到查詢單個訂單的介面,其引數是訂單ID還是編號亦或是其它的資訊,也是需要根據業務來定的,通過這個案例,相信您應該明白了資源倉庫設計的依據是業務而不是資料存盤,DAO才是面向資料的,不同的DAO雖然對應的資料模型不一樣,但有一些基本的功能是通用的比如:增加、洗掉、更新等,由于責任單一且不包含業務邏輯,一般都會將DAO作為基礎設施層中的組件,
|
重點!
|
資源倉庫介面的實作邏輯上屬于基礎設施層的內容,系統設計程序中我個人一般會將其與基礎設施層分開至不同的包中,此外,真實專案中一般也會設計一個資源倉庫的基本介面,畢竟大部分場景中都需要對領域模型進行存盤、變更和根據ID查詢的能力,下面代碼展示了兩個不同業務的資源倉庫介面的定義,您需要注意兩點:1)資源倉庫介面所在的包應該是BO;2)資源倉庫所定義的介面應該由業務來驅動的,
public interface Repository<TID extends Comparable, TEntity extends EntityModel> { /** * 根據ID回傳領域模型 * @param id 領域模型ID * @return 領域模型 */ TEntity findBy(TID id); /** * 洗掉領域物體 * @param entity 待洗掉的領域物體 */ void remove(TEntity entity); /** * 洗掉多個領域物體 * @param entities 待洗掉的領域物體串列 */ void remove(List<TEntity> entities); /** *將領域物體存盤至資源倉庫中 * @param entity 待存盤的領域物體 */ void add(TEntity entity); /** * 將領域物體存盤至資源倉庫中 * @param entities 待存盤的領域物體串列 */ void add(List<TEntity> entities); /** *更新領域物體 * @param entity 待更新的領域物體 */ void update(TEntity entity); /** *更新領域物體 * @param entities 待更新的領域物體串列 */ void update(List<TEntity> entities); }
Repository介面是所有資源倉庫介面的基類,包含了新建、更新、根據ID查詢和洗掉四類基本操作,有人順澩倉庫的介面都應該使用業務術語,類似于“update”、“add”已經偏向于技術,應當使用如“save”代替,我個人覺得這么搞其實挺麻煩的,存盤的時候還需要區分到底是插入還是修改,代碼會很臟,不過使用業務術語表達每一個介面這個倒是個很重要的規范,您應該遵守,另外有爭議的是“delete”介面,這介面其實不應該有,可能也是因為設計時腦子抽了才加上的,您在實踐時干掉即可,有了基本介面后,下面就可以基于此來定義業務級資源倉庫介面,
package xx.workflow.bo.opresourceapply.repository; import xx.common.odd.repository.Repository; import xx.workflow.bo.opresourceapply.OprApplyForm; public interface OprApplyFormRepository extends Repository<Long, OprApplyForm> { }
“OprApplyFormRepository”所面向的物體是“OprApplyForm”,其中沒有再定義任何其它介面,說明只需要使用“Repository”中的能力即可,
package xx.servicedeployment.bo.repository; import xx.common.odd.repository.PersistenceException; import xx.common.odd.repository.Repository; import xx.servicedeployment.bo.DeploymentDetail; public interface DeploymentDetailRepository extends Repository<Long, DeploymentDetail> { /** * 根據部署審批單查詢部署詳情 * @param deploymentApprovalFormId 部署審批單ID * @return 部署詳情 */ DeploymentDetail findByDeploymentApprovalFormId(Long deploymentApprovalFormId) throws PersistenceException; }
“DeploymentDetailRepository”中多了一個“根據部署審批單查詢部署詳情”介面,說明某個命令型業務中有需要根據“部署審批單”查詢“DeploymentDetail”這個業務物體的需求,其它有關“DeploymentDetail”的能力仍然從基類中繼承,
根據上面的演示,您可以看到資源倉庫介面的定義遵循了前面所說的全部規范尤其是其所操作的物件都應是領域模型;您應該也看到了類似查詢“XXX資訊串列”這種單純用于查詢的方法并沒有出現在資源倉庫介面中,
二、業務模型層中的代碼結構
根據上圖所示,您已經明確了BO層所包含的元素的種類,我們前面說BO層很厚,這么多東西都在這個層里,想不厚也不行啊,如果落實到代碼中,這些元素一般會統一放到一個包中,包名即為業務名,如下圖所示,針對包的組織,我建議這么做:根據業務能力將服務分成幾個子BC,以包的形式組織這些BC,比如訂單服務中需要包含兩項業務:訂單管理、發貨單生成,那針對這兩項分別建立兩個包,每個包都按下圖所示的結構進行代碼組織,不建議建立如BO、DAO、VO、Service四個包,根據這些包對代碼進行組織和分類,

上圖展示了“審批服務”業務的代碼結構,BO這個包中除了事件、業務例外和資源倉庫介面,其它的都是物體型別、值型別等組件,根據業務能力組織代碼,您會發現即使是一個單體的系統,在遵循了DDD設計規范后仍能具備高內聚的屬性,后續如果需要拆分時只需要做一些簡單的作業即可,
三、BO層訪問限制
既然BO層處于系統的核心位置,根據六邊型模型的要求就需要在依賴與訪問控制兩個方面進行約束,依賴相對簡單,只要讓其別依賴于其它層就OK,也就是限制這層對其它層元素的參考;特別常見的一個錯誤就是在領域物體中引入資源倉庫介面或DAO,雖然初衷是為了提升性能,但會造成代碼結構的混亂,損害了代碼的健壯性使系統成為了所謂的大泥球(其實球不球的也不是重點,有了BC的隔離最多是個小泥球,胡亂的參考體現出您的作業沒有規則),訪問控制方面,針對每個物件的訪問級別包括public、package、private等需要進行充分考慮,做到最大化的隔離,除應用服務層和資源倉庫實作層,其它層不可以直接參考業務模型,下面的代碼展示了BO層中實作領域模型時的反例,供參考,

問題一:業務模型依賴Spring框架;問題二:訪問了其它包的DAO;問題三:反向依萊澩倉庫,記住:資源倉庫實作與領域模型的依賴是單向的,
總結
本章講解了BO層中所包含的元素,尤其對于業務例外和資源倉庫介面進行了重點說明,通過本章的內容相信您已經對于所謂的六邊型架構中的內核及其構成有了一個感性的認識,也為后面的學習打下了一定的基礎,后續我們會深入到BO內部對各元素逐一的進行解釋,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/445551.html
標籤:其他
下一篇:重新認識受控和非受控組件
