架構(gòu)上如何設(shè)計(jì)領(lǐng)域模型和數(shù)據(jù)模型?
依稀記得我第一次設(shè)計(jì)一個(gè)系統(tǒng)的時(shí)候,畫(huà)了一堆UML(Unified Modeling Language,統(tǒng)一建模語(yǔ)言)圖,面對(duì)Class Diagram(其實(shí)就是領(lǐng)域模型),糾結(jié)了好久,不知道如何落地。 因?yàn)?,如果按照這個(gè)類(lèi)圖去落數(shù)據(jù)庫(kù)的話,看起來(lái)很奇怪,有點(diǎn)繁瑣。 可是不按照這個(gè)類(lèi)圖落庫(kù)的話,又不知道這個(gè)類(lèi)圖畫(huà)了有什么用。
捫心自問(wèn),你有多久沒(méi)有畫(huà)數(shù)據(jù)模型和領(lǐng)域模型了?
現(xiàn)在回想起來(lái),我當(dāng)時(shí)的糾結(jié)源自于我對(duì)領(lǐng)域模型和數(shù)據(jù)模型這兩個(gè)重要概念的不清楚,先前看過(guò)DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)),里面一句話我覺(jué)得講的很不錯(cuò),一個(gè)類(lèi)型可以充當(dāng)多個(gè)角色,這個(gè)角色可以是顯式的(實(shí)現(xiàn)了某個(gè)接口或基類(lèi)),也可以是隱式的(承擔(dān)的具體職責(zé)和上下文決定)。
但是每次在新的需求下來(lái),出設(shè)計(jì)方案的時(shí)候在數(shù)據(jù)模型和領(lǐng)域模型上會(huì)耽誤一些時(shí)間,根本原因在于對(duì)這兩個(gè)概念混淆。也因?yàn)槿绱?,在設(shè)計(jì)方案或者在開(kāi)發(fā)過(guò)程中會(huì)頻繁修改數(shù)據(jù)模型的設(shè)計(jì),因?yàn)槿绻讓拥倪壿嫛⒏拍?、理論基礎(chǔ)沒(méi)搞清楚的話,其構(gòu)建在其上的系統(tǒng)也會(huì)出現(xiàn)問(wèn)題,非常嚴(yán)重的問(wèn)題。
借鑒DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))的一些設(shè)計(jì)原則,我覺(jué)得有必要花時(shí)間認(rèn)真明晰這兩個(gè)概念,幫助大家在工作中,更好的做設(shè)計(jì)決策。
一 概念定義
數(shù)據(jù)模型:面向持久化,數(shù)據(jù)的載體。關(guān)注的是領(lǐng)域知識(shí),是業(yè)務(wù)領(lǐng)域的核心實(shí)體,體現(xiàn)了問(wèn)題域里面的關(guān)鍵概念,以及概念之間的聯(lián)系。領(lǐng)域模型建模的關(guān)鍵是看模型能否顯性化、清晰的表達(dá)業(yè)務(wù)語(yǔ)義,擴(kuò)展性是其次。
領(lǐng)域模型:面向業(yè)務(wù),行為的載體。關(guān)注的是數(shù)據(jù)存儲(chǔ),所有的業(yè)務(wù)都離不開(kāi)數(shù)據(jù),都離不開(kāi)對(duì)數(shù)據(jù)的CRUD,數(shù)據(jù)模型建模的決策因素主要是擴(kuò)展性、性能等非功能屬性,無(wú)需過(guò)分考慮業(yè)務(wù)語(yǔ)義的表征能力。
一個(gè)強(qiáng)調(diào)的是實(shí)體,另一個(gè)強(qiáng)調(diào)的是關(guān)系,再細(xì)想下我們當(dāng)初建模的時(shí)候都是用啥的啥圖-ER圖,這下子就被慢慢帶偏了。設(shè)計(jì)的數(shù)據(jù)模型里面帶了實(shí)體聲明也帶了業(yè)務(wù)關(guān)系,兩者開(kāi)始混淆。
是的,二者的確有一些共同點(diǎn),有時(shí)候領(lǐng)域模型和數(shù)據(jù)模型會(huì)長(zhǎng)的很像,甚至?xí)呁@很正常。但更多的時(shí)候,二者是有區(qū)別的。正確的做法應(yīng)該是有意識(shí)地把這兩個(gè)模型區(qū)別開(kāi)來(lái),分別設(shè)計(jì),因?yàn)樗麄兘5哪繕?biāo)會(huì)有所不同。
如下圖所示,數(shù)據(jù)模型負(fù)責(zé)的是數(shù)據(jù)存儲(chǔ)面向DB,其要義是擴(kuò)展性、靈活性、性能。而領(lǐng)域模型負(fù)責(zé)業(yè)務(wù)邏輯的實(shí)現(xiàn),其要義是業(yè)務(wù)語(yǔ)義顯性化的表達(dá),以及充分利用OO的特性增加代碼的業(yè)務(wù)表征能力。
途中標(biāo)識(shí)灰色的部分其實(shí)還可以細(xì)分,業(yè)務(wù)到模型之間也可進(jìn)行拆分,涉及到一些命名,這里就不做展開(kāi)。
在日常開(kāi)發(fā)過(guò)程中,我們?cè)诤芏嗟南到y(tǒng)業(yè)務(wù)設(shè)計(jì)上,并沒(méi)很好的處理數(shù)據(jù)模型和領(lǐng)域模型的關(guān)系,反而在設(shè)計(jì)的時(shí)候一個(gè)是把數(shù)據(jù)模型當(dāng)領(lǐng)域模型,另一個(gè)是把領(lǐng)域模型當(dāng)數(shù)據(jù)模型。
二 錯(cuò)把領(lǐng)域模型當(dāng)數(shù)據(jù)模型
最近在優(yōu)化低代碼那塊的元數(shù)據(jù)優(yōu)化,里面涉及到一些元數(shù)據(jù)存儲(chǔ)、拓展問(wèn)題。這塊邏輯大致可以簡(jiǎn)單概括:
數(shù)據(jù)表單設(shè)計(jì)時(shí)候,用戶可以動(dòng)態(tài)配置列的屬性以及對(duì)列屬性根據(jù)對(duì)應(yīng)的數(shù)據(jù)類(lèi)型動(dòng)態(tài)匹配相應(yīng)函數(shù)。
對(duì)于這個(gè)規(guī)則,領(lǐng)域模型很簡(jiǎn)單,就是提供了列基本配置信息和屬性配置信息配置數(shù)據(jù),如下圖所示:
如果按照這個(gè)思路下去就會(huì)存在兩張表meta_field_definition、meta_field_attribute 兩張數(shù)據(jù)表,一張用來(lái)存儲(chǔ)列的基礎(chǔ)定義,另一張用來(lái)定義列的屬性配置以及拓展。
如果我們這個(gè)干了,我們就犯了把領(lǐng)域模型當(dāng)數(shù)據(jù)模型的錯(cuò)誤,這里設(shè)計(jì)一張數(shù)據(jù)表足夠。在原來(lái)的元數(shù)據(jù)列定義表里面加屬性配置字段fd_attribute 以Json的形式存儲(chǔ),再基礎(chǔ)表單的基礎(chǔ)上加拓展表fd_extend_feature(當(dāng)前業(yè)務(wù)用不上作為基礎(chǔ)保留的拓展字段)
調(diào)整后有什么好處:
-
首先,一張表單的維護(hù)成本肯定比多張表的維護(hù)成本低
-
其次,其數(shù)據(jù)的擴(kuò)展性更好。比如:針對(duì)某種數(shù)據(jù)類(lèi)型要支持某種定制的業(yè)務(wù)配置和函數(shù)支持,如果是一張表,我們就需要往屬性表里面繼續(xù)添加新的業(yè)務(wù)支持配置。但是如果我們修改為一張表在原有的元數(shù)據(jù)中保持不變?cè)趯傩酝卣估锩嬉訨SON格式添加配置即可。
可是,在業(yè)務(wù)代碼里面,如果是基于JSON在做事情可不那么美好。我們需要把JSON的數(shù)據(jù)對(duì)象,轉(zhuǎn)換成有業(yè)務(wù)語(yǔ)義的領(lǐng)域?qū)ο?,這樣,我們既可以享受數(shù)據(jù)模型擴(kuò)展性帶來(lái)的便捷性,又不失領(lǐng)域模型對(duì)業(yè)務(wù)語(yǔ)義顯性化帶來(lái)的代碼可讀性。
三 錯(cuò)把數(shù)據(jù)模型當(dāng)領(lǐng)域模型
的確,數(shù)據(jù)模型最好盡量可擴(kuò)展,畢竟,改動(dòng)數(shù)據(jù)庫(kù)可是個(gè)大工程,不管是加字段、減字段,還是加表、刪表,都涉及到不少的工作量。
拿上面的案例來(lái)講
可以注意到fd_extend_feature 拓展表所創(chuàng)建的,便于對(duì)表的垂直拓展補(bǔ)充。JSON字段也好,垂直表也好,雖然可以很好的解決數(shù)據(jù)存儲(chǔ)擴(kuò)展的問(wèn)題,但是,我們最好不要把這些擴(kuò)展(features)當(dāng)成領(lǐng)域?qū)ο髞?lái)處理,否則,你的代碼根本就不是在面向?qū)ο缶幊蹋窃诿嫦驍U(kuò)展字段(features)編程,從而犯了把數(shù)據(jù)模型當(dāng)領(lǐng)域模型的錯(cuò)誤。更好的做法,應(yīng)該是把數(shù)據(jù)對(duì)象(Data Object)轉(zhuǎn)換成領(lǐng)域?qū)ο髞?lái)處理。但是在處理改字段的時(shí)候,如果頻繁操作addFdExtendFeature、getFdExtendFeature是一種典型的把數(shù)據(jù)模型當(dāng)領(lǐng)域模型的錯(cuò)誤示范。
四 總結(jié)
在日常設(shè)計(jì)和開(kāi)發(fā)中我們應(yīng)該是把領(lǐng)域模型、數(shù)據(jù)模型區(qū)別開(kāi)來(lái),讓他們各司其職,從而更合理的架構(gòu)我們的應(yīng)用系統(tǒng)。其中,領(lǐng)域模型是面向領(lǐng)域?qū)ο蟮?,要盡量具體,盡量語(yǔ)義明確,顯性化的表達(dá)業(yè)務(wù)語(yǔ)義是其首要任務(wù),擴(kuò)展性是其次。而數(shù)據(jù)模型是面向數(shù)據(jù)存儲(chǔ)的,要盡量可擴(kuò)展。
回歸到主題一個(gè)類(lèi)型可以充當(dāng)多個(gè)角色,這個(gè)角色可以是顯式的(實(shí)現(xiàn)了某個(gè)接口或基類(lèi)),也可以是隱式的(承擔(dān)的具體職責(zé)和上下文決定),
-
數(shù)據(jù)模型:面向持久化,數(shù)據(jù)的載體。
-
領(lǐng)域模型:面向業(yè)務(wù),行為的載體。