天天看點

領域模型vs資料模型,應該怎麼用?

領域模型vs資料模型,應該怎麼用?

作者 | 張建飛

來源 | 阿裡技術公衆号

依稀記得我第一次設計一個系統的時候,畫了一堆UML(Unified Modeling Language,統一模組化語言)圖,面對Class Diagram(其實就是領域模型),糾結了好久,不知道如何落地。因為,如果按照這個類圖去落資料庫的話,看起來很奇怪,有點繁瑣。可是不按照這個類圖落庫的話,又不知道這個類圖畫了有什麼用。

現在回想起來,我當時的糾結源自于我對領域模型和資料模型這兩個重要概念的不清楚。最近,我發現對這兩個概念的混淆不是個例,而是非常普遍的現象。其結果就是,小到會影響一些子產品設計的不合理性,大到會影響像業務中台這樣重大技術決策,因為如果底層的邏輯、概念、理論基礎沒搞清楚的話,其建構在其上的系統也會出現問題,非常嚴重的問題。

鑒于很少看到有人對這個話題進行比較深入的研究和探讨,我覺得有必要花時間認真明晰這兩個概念,幫助大家在工作中,更好的做設計決策。

一 領域模型和資料模型的概念定義

領域模型關注的是領域知識,是業務領域的核心實體,展現了問題域裡面的關鍵概念,以及概念之間的聯系。領域模型模組化的關鍵是看模型能否顯性化、清晰的表達業務語義,擴充性是其次。

資料模型關注的是資料存儲,所有的業務都離不開資料,都離不開對資料的CRUD,資料模型模組化的決策因素主要是擴充性、性能等非功能屬性,無需過分考慮業務語義的表征能力。

按照Robert在《整潔架構》裡面的觀點,領域模型是核心,資料模型是技術細節。然而現實情況是,二者都很重要。

這兩個模型之是以容易被混淆,是因為兩者都強調實體(Entity),都強調關系(Relationship),這可不,我們傳統的資料庫的資料模型模組化就是用的ER圖啊。

是的,二者的确有一些共同點,有時候領域模型和資料模型會長的很像,甚至會趨同,這很正常。但更多的時候,二者是有差別的。正确的做法應該是有意識地把這兩個模型差別開來,分别設計,因為他們模組化的目标會有所不同。如下圖所示,資料模型負責的是資料存儲,其要義是擴充性、靈活性、性能。而領域模型負責業務邏輯的實作,其要義是業務語義顯性化的表達,以及充分利用OO的特性增加代碼的業務表征能力。

領域模型vs資料模型,應該怎麼用?

然而,現實情況是,我們很多的業務系統設計,并沒有很好的區分二者的關系。經常會犯兩個錯誤,一個是把領域模型當資料模型,另一個是把資料模型當領域模型。

二 錯把領域模型當資料模型

這幾天我在做一個報價優化的項目,裡面涉及到報價規則的問題,這塊的業務邏輯大意是說,對于不同的商品(通過類目、品牌、供應商類型等次元區分),我們會給出不同的價格區間,然後來判斷商家的報價是否應該被自動稽核(autoApprove)通過,還是應該被自動攔截(autoBlock)。

對于這個規則,領域模型很簡單,就是提供了價格管控需要的配置資料,如下圖所示:

領域模型vs資料模型,應該怎麼用?

如果按照這個領域模型去設計我們的存儲的話,自然是需要兩張表:price_rule和price_range,一張用來存價格規則,一張是用來存價格區間。

領域模型vs資料模型,應該怎麼用?

如果這樣去設計資料模型,我們就犯了把領域模型當資料模型的錯誤。這裡,更合适的做法是一張表就夠了,把price_range作為一個字段在price_rule中用一個字段存儲,如下圖所示,裡面的多個價格區間資訊用一個JSON字段去存取就好了。

領域模型vs資料模型,應該怎麼用?

這樣做的好處很明顯:

  • 首先,維護一張資料庫表肯定比兩張的成本要小。
  • 其次,其資料的擴充性更好。比如,新需求來了,需要增加一個建議價格(suggest price)區間,如果是兩張表的話,我需要在price_range中加兩個新字段,而如果是JSON存儲的話,資料模型可以保持不變。

可是,在業務代碼裡面,如果是基于JSON在做事情可不那麼美好。我們需要把JSON的資料對象,轉換成有業務語義的領域對象,這樣,我們既可以享受資料模型擴充性帶來的便捷性,又不失領域模型對業務語義顯性化帶來的代碼可讀性。

領域模型vs資料模型,應該怎麼用?

三 錯把資料模型當領域模型

的确,資料模型最好盡量可擴充,畢竟,改動資料庫可是個大工程,不管是加字段、減字段,還是加表、删表,都涉及到不少的工作量。

說到資料模型的擴充設計經典之作,非阿裡的業務中台莫屬,核心的商品、訂單、支付、物流4張表,得益于良好的擴充性設計,就支撐了阿裡幾十個業務的成千上萬的業務場景。

拿商品中台來說,它用一張auction_extend垂直表,就解決了所有業務商品資料存儲擴充性的需求。理論上來說,這種資料模型可以滿足無限的業務擴充。

JSON字段也好,垂直表也好,雖然可以很好的解決資料存儲擴充的問題,但是,我們最好不要把這些擴充(features)當成領域對象來處理,否則,你的代碼根本就不是在面向對象程式設計,而是在面向擴充字段(features)程式設計,進而犯了把資料模型當領域模型的錯誤。更好的做法,應該是把資料對象(Data Object)轉換成領域對象來處理。

如下所示,這種代碼裡面到處是getFeature、addFeature的寫法,是一種典型的把資料模型當領域模型的錯誤示範。

領域模型vs資料模型,應該怎麼用?

四 領域模型和資料模型各司其職

上面展示了因為混淆領域模型和資料模型,帶來的問題。正确的做法應該是把領域模型、資料模型差別開來,讓他們各司其職,進而更合理的架構我們的應用系統。

其中,領域模型是面向領域對象的,要盡量具體,盡量語義明确,顯性化的表達業務語義是其首要任務,擴充性是其次。而資料模型是面向資料存儲的,要盡量可擴充。

在具體落地的時候,我們可以采用COLA[1]的架構思想,使用gateway作為資料對象(Data Object)和領域對象(Entity)之間的轉義網關,其中,gateway除了轉義的作用,還起到了防腐解耦的作用,解除了業務代碼對底層資料(DO、DTO等)的直接依賴,進而提升系統的可維護性。

領域模型vs資料模型,應該怎麼用?

此外,教科書上教導我們在做關系資料庫設計的時候,要滿足3NF(三範式),然而,在實際工作中,我們經常會因為性能、擴充性的原因故意打破這個原則,比如我們會通過資料備援提升通路性能,我們會通過中繼資料、垂直表、擴充字段提升表的擴充性。

業務場景不一樣,對資料擴充的訴求也不一樣,像price_rule這種簡單的配置資料擴充,JSON就能勝任。複雜一點的,像auction_extend這種垂直表也是不錯的選擇。

wait,有同學說,你這樣做,資料是可擴充了,可資料查詢怎麼解決呢?總不能用join表,或者用like吧。實際上,對一些配置類的資料,或者資料量不大的資料,完全可以like。然而,對于像阿裡商品、交易這樣的海量資料,當然不能like,不過這個問題,很容易通過讀寫分離,建構search的辦法解決。

領域模型vs資料模型,應該怎麼用?

五 關于擴充的更多思考

最後,再給一個思考題吧。

前面提到的資料擴充,還都是領域内的有限擴充。如果我連業務領域是什麼還不知道,能不能做資料擴充呢?可以的,Salesforce的force.com就是這麼做的,其底層資料存儲完全是中繼資料驅動的(metadata-driven[2]),他用一張有500個匿名字段的表,去支撐所有的SaaS業務,每個字段的實際表意是通過中繼資料去描述的。如下圖所示,value0到value500都是預留的業務字段,具體代表什麼意思,由metadata去定義。

領域模型vs資料模型,應該怎麼用?

說實話,這種實作方式的确是一個很有想法,很大膽的設計,也的确支撐了上面數以千計的SaaS應用和Salesforce千億美金的市值。

隻是,我不清楚從中繼資料到領域對象的映射,Salesforce具體是怎麼做的,是通過他們的文法糖Apex?如果沒有領域對象,他們的業務代碼要怎麼寫呢?反正據在Salesforce裡面做vendor的同學說,他們所謂的Low-Code,裡面還是有很多用Apex寫的代碼,而且可維護性一般。

anyway,我們絕大部分的應用都是面向确定問題域的,不需要像Salesforce那樣提供“無邊際”的擴充能力。在這種情況下,我認為,領域對象是最好的連接配接資料模型和業務邏輯的橋梁。

相關連結

[1]

https://github.com/alibaba/COLA [2] https://developer.salesforce.com/wiki/multi_tenant_architecture