依稀記得我第一次設計乙個系統的時候,畫了一堆uml圖,面對class diagram(其實就是領域模型),糾結了好久,不知道如何落地。因為,如果按照這個類圖去落資料庫的話,看起來很奇怪,有點繁瑣。可是不按照這個類圖落庫的話,又不知道這個類圖畫了有什麼用。
現在回想起來,我當時的糾結源自於我對領域模型和資料模型這兩個重要概念的不清楚。最近,我發現對這兩個概念的混淆不是個例,而是非常普遍的現象。其結果就是,小到會影響一些模組設計的不合理性,大到會影響像業務中臺這樣重大技術決策,因為如果底層的邏輯、概念、理論基礎沒搞清楚的話,其構建在其上的系統也會出現問題,非常嚴重的問題。
鑑於很少看到有人對這個話題進行比較深入的研究和**,我覺得有必要花時間認真明晰這兩個概念,幫助大家在工作中,更好的做設計決策。
領域模型和資料模型的概念定義
領域模型關注的是領域知識,是業務領域的核心實體,體現了問題域裡面的關鍵概念,以及概念之間的聯絡。領域模型建模的關鍵是看模型能否顯性化、清晰的表達業務語義,擴充套件性是其次。
資料模型關注的是資料儲存,所有的業務都離不開資料,都離不開對資料的crud,資料模型建模的決策因素主要是擴充套件性、效能等非功能屬性,無需過分考慮業務語義的表徵能力。
按照robert在《整潔架構》裡面的觀點,領域模型是核心,資料模型是技術細節。然而現實情況是,二者都很重要。
這兩個模型之所以容易被混淆,是因為兩者都強調實體(entity),都強調關係(relationship),這可不,我們傳統的資料庫的資料模型建模就是用的er圖啊。
是的,二者的確有一些共同點,有時候領域模型和資料模型會長的很像,甚至會趨同,這很正常。但更多的時候,二者是有區別的。正確的做法應該是有意識的把這兩個模型區別開來,分別設計,因為他們建模的目標會有所不同。如下圖所示,資料模型負責的是資料儲存,其要義是擴充套件性、靈活性、效能。而領域模型負責業務邏輯的實現,其要義是業務語義顯性化的表達,以及充分利用oo的特性增加**的業務表徵能力。
然而,現實情況是,我們很多的業務系統設計,並沒有很好的區分二者的關係。經常會犯兩個錯誤,乙個是把領域模型當資料模型,另乙個是把資料模型當領域模型。
對於這個規則,領域模型很簡單,就是提供了**管控需要的配置資料,如下圖所示:
如果按照這個領域模型去設計我們的儲存的話,自然是需要兩張表:price_rule和price_range,一張用來存**規則,一張是用來存**區間。
如果這樣去設計資料模型,我們就犯了把領域模型當資料模型的錯誤。這裡,更合適的做法是一張表就夠了,把price_range作為乙個欄位在price_rule中用乙個字段儲存,如下圖所示,裡面的多個**區間資訊用乙個json欄位去訪問就好了。
這樣做的好處很明顯:
首先,維護一張資料庫表肯定比兩張的成本要小。
其次,其資料的擴充套件性更好。比如,新需求來了,需要增加乙個建議**(suggest price)區間,如果是兩張表的話,我需要在price_range中加兩個新字段,而如果是json儲存的話,資料模型可以保持不變。
可是,在業務**裡面,如果是基於json在做事情可不那麼美好。我們需要把json的資料物件,轉換成有業務語義的領域物件,這樣,我們既可以享受資料模型擴充套件性帶來的便捷性,又不失領域模型對業務語義顯性化帶來的**可讀性。
錯把資料模型當領域模型
的確,資料模型最好盡量可擴充套件,畢竟,改動資料庫可是個大工程,不管是加欄位、減欄位,還是加表、刪表,都涉及到不少的工作量。
說到資料模型的擴充套件設計經典之作,非阿里的業務中颱莫屬,核心的商品、訂單、支付、物流4張表,得益於良好的擴充套件性設計,就支撐了阿里幾十個業務的成千上萬的業務場景。
拿商品中臺來說,它用一張auction_extend垂直表,就解決了所有業務商品資料儲存擴充套件性的需求。理論上來說,這種資料模型可以滿足無限的業務擴充套件。
json欄位也好,垂直表也好,雖然可以很好的解決資料儲存擴充套件的問題。但是,我們最好不要把這些擴充套件(features)當成領域物件來處理,否則,你的**根本就不是在物件導向程式設計,而是在面向擴充套件字段(features)程式設計,從而犯了把資料模型當領域模型的錯誤。更好的做法,應該是把資料物件(data object)轉換成領域物件來處理。
如下所示,這種**裡面到處是getfeature、addfeature的寫法,是一種典型的把資料模型當領域模型的錯誤示範。
上面展示的**,是乙個在某中台上寫業務**的同學,在離職那天發給我看的,他說他受夠了這種亂七八糟的**,但作為乙個底層小p,又無能改變局面,無奈之下,只能選擇離開。
領域模型和資料模型各司其職
上面展示了因為混淆領域模型和資料模型,帶來的問題。正確的做法應該是把領域模型、資料模型區別開來,讓他們各司其職,從而更合理的架構我們的應用系統。
其中,領域模型是面向領域物件的,要盡量具體,盡量語明確,顯性化的表達業務語義是其首要任務,擴充套件性是其次。而資料模型是面向資料儲存的,要盡量可擴充套件。
在具體落地的時候,我們可以採用cola的架構思想,使用gateway作為資料物件(data object)和領域物件(entity)之間的轉義閘道器,其中,gateway除了轉義的作用,還起到了防腐解耦的作用,解除了業務**對底層資料(do、dto等)的直接依賴,從而提公升系統的可維護性。
此外,教科書上教導我們在做關聯式資料庫設計的時候,要滿足3nf(三正規化),然而,在實際工作中,我們經常會因為效能、擴充套件性的原因故意打破這個原則,比如我們會通過資料冗餘提公升訪問效能,我們會通過元資料、垂直表、擴充套件字段提公升表的擴充套件性。
業務場景不一樣,對資料擴充套件的訴求也不一樣,像price_rule這種簡單的配置資料擴充套件,json就能勝任。複雜一點的,像auction_extend這種垂直表也是不錯的選擇。
wait,有同學說,你這樣做,資料是可擴充套件了,可資料查詢怎麼解決呢?總不能用join表,或者用like吧,實際上,對一些配置類的資料,或者資料量不大的資料,完全可以like。然而,對於像阿里商品、交易這樣的海量資料,當然不能like,不過這個問題,很容易通過讀寫分離,構建search的辦法解決。
關於擴充套件的更多思考
最後,再給乙個思考題吧。
前面提到的資料擴充套件,還都是領域內的有限擴充套件。如果我連業務領域是什麼還不知道,能不能做資料擴充套件呢? 可以的,salesforce的force.com就是這麼做的,其底層資料儲存完全是元資料驅動的(metadata-driven),他用一張有500個匿名欄位的表,去支撐所有的saas業務,每個欄位的實際表意是通過元資料去描述的。如下圖所示,value0到value500都是預留的業務字段,具體代表什麼意思,由metadata去定義。
說實話,這種實現方式的確是乙個很有想法,很大膽的設計,也的確支撐了上面數以千計的saas應用和salesforce千億美金的市值。
只是,我不清楚從元資料到領域物件的對映,salesforce具體是怎麼做的,是通過他們的語法糖apex?如果沒有領域物件,他們的業務**要怎麼寫呢?反正據在salesforce裡面做vendor的同學說,他們所謂的low code,裡面還是有很多用apex寫的**,而且可維護性一般。
一文教你 Mysql資料備份
按照備份時對資料庫的影響範圍分為 cold backup 冷備 指在資料庫停止的情況下進行備份 offlinebackup 官方手冊稱為離線備份 warm backup 溫備 備份同樣在資料庫執行時進行,但是會對當前資料庫的操作有所影響,例如 加乙個全域性讀鎖以保證備份資料的一致性。按照備份後的檔案...
一文教你玩轉git
首先進入乙個目錄之後,使用這個命令是先初始化乙個git倉庫 git init 它會預設建立乙個名為master的分支 下面這個用於檢視是否有修改的檔案,如果有就會報紅 git status 下面這個用於檢視檔案修改的細節 git diff test.txt 下面這個是提交到暫存區 git add t...
一文教你看懂原型!!!
談到原型,我們都知道最重要的兩個屬性就是 proto 和prototype,那麼他們到底有什麼關係又到底是什麼呢,這一篇看完相信你就會有一些理解了。js中萬物皆物件,每個資料都會有乙個 proto 的屬性,這個屬性叫隱式原型。乙個物件 obj 的 隱式原型 proto 指向構造該物件 obj 的 建...