DDD實戰(zhàn)中如何避免過度設計
DDD即領域驅(qū)動設計,是一種建模方法論,強調(diào)通過分析建模、再設計實現(xiàn),而不是數(shù)據(jù)庫表驅(qū)動。
DDD解決的是核心復雜業(yè)務設計,特別強調(diào)的是“核心”與”復雜”,DDD只適用于業(yè)務系統(tǒng)。
DDD簡化業(yè)務系統(tǒng)的實現(xiàn),讓業(yè)務邏輯高度內(nèi)聚,聚合之間通過聚合根ID引用與領域事件松耦合,正是高內(nèi)聚低耦合能讓項目代碼隨著業(yè)務需求的不斷迭代保持整潔。
通過四層架構(gòu)設計/六邊形架構(gòu)設計實現(xiàn)與基礎設施、框架的解偶。
DDD也解決了微服務的拆分問題。
DDD解決的是業(yè)務問題,通過事件風暴識別領域事件、識別限界上下文、識別問題子域、聚合,再對聚合建模。并非解決數(shù)據(jù)查詢問題、數(shù)據(jù)分析問題。
前面我們了解了如何通過CQRS解決讀寫分離問題,然而……
在實戰(zhàn)DDD過程中我們總會遇到一些即不適合領域建模也不能簡單通過CQRS解決的問題,如為前端提供住址、性別等選項的接口如何實現(xiàn)、用戶標簽功能如何設計、評價功能如何設計……
對于這些看似簡單的問題,我們要考慮的是如何避免過度設計,保持架構(gòu)的簡潔以及擴展。
為前端提供選項有非常多的場景。有些選項是個枚舉類型,如性別、訂單類型;有些選項需要支持增加或刪除,如商品分類,雖然實體需要通過ID引用它,但卻又不適合作為實體存在。
無論是枚舉類型的選項,還是通過ID引用卻又不適合作為實體存在的選項,都應該作為值對象。可以直接在應用層讀寫存儲/緩存中間件,如mysql、es、redis。
下文中會出現(xiàn)的領域(業(yè)務)名詞:達人:網(wǎng)紅、紅人,有粉絲群體的個人、有一定影響力的個人;OTO探店:線上報名線下探店,線上推廣(主要服務美食點,為商家推廣線下店鋪);
我們項目中有需要實現(xiàn)為達人打標簽的需求,在接到這個需求時,我們首先做的就是分析標簽能產(chǎn)生什么樣的價值,再通過事件風暴看是否能分析出領域事件。
結(jié)合項目業(yè)務分析,達人標簽在我們項目中發(fā)揮的價值其實就是用于興趣匹配推送,在OTO探店業(yè)務場景下,興趣匹配可以理解為根據(jù)達人喜好的口味給達人推送相關店鋪的訂單。
分析到這里我們就清楚了標簽功能要做的就是數(shù)據(jù)分析還有算法推薦,除此之外,標簽沒有任何領域事件(行為、業(yè)務動作),標簽不存在領域上下文,所以不應該過度設計。
另外,為達人打標簽/達人自己打標簽,那么標簽就是達人的值對象,與達人的性別、年齡、住址等屬性都是達人的值對象。達人可以添加標簽或者刪除標簽。
所以最終我們實現(xiàn)的標簽功能就只是在達人的聚合根上添加一個標簽值對象。給達人聚合根添加修改標簽的方法。
另外我們需要為前端提供標簽選項,支持管理員添加或刪除標簽,與實現(xiàn)普通選項一樣,我們單獨在達人聚合下提供一個應用層的LableService,直接操作數(shù)據(jù)庫增/刪標簽。
標簽表的存在只是提供標簽選項,達人給自己打標簽只能從系統(tǒng)提供的標簽中選擇,標簽相當于只是選項。因此達人的標簽值對象并非存標簽的id,而是直接存標簽的名稱。
當標簽選項刪除時我們不應該為所有打了該標簽的用戶默認刪除他的標簽,這實際是不允許的,就像我們不能隨便改用戶的性別、住址一樣。
只有在達人更新標簽時不提供已經(jīng)刪除的選項,讓達人自己選擇新的標簽更新,更新后舊的標簽全部被替換也就達到刪除的目的。
除選項、標簽外,我們要做的還有一個評價功能。
根據(jù)事件風暴我們分析出以下領域事件: a、達人完成任務后商家可評價達人(每個任務只允許評價一次) b、商家評價達人后更新達人的得分(如內(nèi)容質(zhì)量得分) "商家評價達人"涉及到任務、達人、商家三個維度,達人可以查看商家給自己的評價,商家也可以查看自己給哪些達人寫了評價,并且都能查看任務關聯(lián)的評價。
顯然,無論是把評價功能放到任務上下文還是達人上下文、商家上下文,都不合理。因此我們?yōu)樵u價劃分出評價上下文,識別出評價問題子域。 商家評價達人作為評價上下文的一個聚合,后期可能還會實現(xiàn)支持達人評價店鋪,而達人評價店鋪又是一個評價上下文的聚合,評價上下文存在多個聚合。
我們在實戰(zhàn)DDD過程中,在實現(xiàn)前端選項、標簽功能、評價功能過程中也做了詳細的分析再設計,目的都是避免過度設計。
不斷摸索……