新聞中心
過去,系統(tǒng)的軟件設計是以數(shù)據(jù)庫設計為核心,當需求確定下來以后,團隊首先開始進行數(shù)據(jù)庫設計。因為數(shù)據(jù)庫是各個模塊唯一的接口,當整個團隊將數(shù)據(jù)庫設計確定下來以后,就可以按照模塊各自獨立地進行開發(fā)了,如下圖所示。

成都創(chuàng)新互聯(lián)公司是一家以網(wǎng)站建設公司、網(wǎng)頁設計、品牌設計、軟件運維、seo優(yōu)化排名、小程序App開發(fā)等移動開發(fā)為一體互聯(lián)網(wǎng)公司。已累計為成都電動窗簾等眾行業(yè)中小客戶提供優(yōu)質(zhì)的互聯(lián)網(wǎng)建站和軟件開發(fā)服務。
在上面的過程中,為了提高團隊開發(fā)速度,盡量讓各個模塊不要交互,從而達到各自獨立開發(fā)的效果。 但是,隨著系統(tǒng)規(guī)模越來越大,業(yè)務邏輯越來越復雜,我們越來越難于保證各個模塊獨立不交互了。
隨著軟件業(yè)的不斷發(fā)展,軟件系統(tǒng)變得越來越復雜,各個模塊間的交互也越來越頻繁,這時,原有的設計過程已經(jīng)不能滿足我們的需要了。因為如果要先進行數(shù)據(jù)庫設計,但數(shù)據(jù)庫設計只能描述數(shù)據(jù)結構,而不能描述系統(tǒng)對這些數(shù)據(jù)結構的處理。因此,在第一次對整個系統(tǒng)的梳理過程中,只能梳理系統(tǒng)的所有數(shù)據(jù)結構,形成數(shù)據(jù)庫設計;接著,又要再次梳理整個系統(tǒng),分析系統(tǒng)對這些數(shù)據(jù)結構的處理過程,形成程序設計。為什么不能一次性地把整個系統(tǒng)的設計梳理到位呢?
現(xiàn)如今,我們已經(jīng)按照面向?qū)ο蟮能浖O計過程來分析設計系統(tǒng)了。 當開始需求分析時,首先進行用例模型的設計,分析整個系統(tǒng)要實現(xiàn)哪些功能; 接著進行領域模型的設計,分析系統(tǒng)的業(yè)務實體。 在領域模型分析中,采用類圖的形式,每個類可以通過它的屬性來表述數(shù)據(jù)結構,又可以通過添加方法來描述對這個數(shù)據(jù)結構的處理。 因此,在領域模型的設計過程中,既完成了對數(shù)據(jù)結構的梳理,又確定了系統(tǒng)對這些數(shù)據(jù)結構的處理,這樣就把兩項工作一次性地完成了。
在這個設計過程中,其核心是領域模型的設計。以領域模型作為核心,可以指導系統(tǒng)的數(shù)據(jù)庫設計與程序設計,此時,數(shù)據(jù)庫設計就弱化為了領域?qū)ο蟪志没O計的一種實現(xiàn)方式。
一、領域?qū)ο蟪志没乃枷?/h4>
什么叫領域?qū)ο蟮某志没兀吭诋斀褴浖軜嬙O計的主流思想中,面向?qū)ο笤O計成了主流思想,在整個系統(tǒng)運行的過程中,所有的數(shù)據(jù)都是以領域?qū)ο蟮男问酱嬖诘?。譬如?/p>
- 要插入一條記錄就是創(chuàng)建一個領域?qū)ο螅?/li>
- 要更新一條記錄就是根據(jù) key 值去修改相應的領域?qū)ο螅?/li>
- 刪除數(shù)據(jù)則是摧毀這個領域?qū)ο蟆?/li>
假如我們的服務器是一臺超級強大的服務器,那實際上不需要任何數(shù)據(jù)庫,直接操作這些領域?qū)ο缶涂梢粤?,但在現(xiàn)實世界中沒有那么強大的服務器。因此,必須將暫時不用的領域?qū)ο蟪志没鎯Φ酱疟P中,而數(shù)據(jù)庫只是這種持久化存儲的一種實現(xiàn)方式。
按照這種設計思想,我們將暫時不使用的領域?qū)ο髲膬?nèi)存中持久化存儲到磁盤中。當日后需要再次使用這個領域?qū)ο髸r,根據(jù) key 值到數(shù)據(jù)庫查找到這條記錄,然后將其恢復成領域?qū)ο?,應用程序就可以繼續(xù)使用它了,這就是領域?qū)ο蟪志没鎯Φ脑O計思想。
所以,今天的數(shù)據(jù)庫設計,實際上就是將領域?qū)ο蟮脑O計按照某種對應關系,轉(zhuǎn)換成數(shù)據(jù)庫的設計。同時,隨著整個產(chǎn)業(yè)的大數(shù)據(jù)轉(zhuǎn)型,今后的數(shù)據(jù)庫設計思想也將發(fā)生巨大的轉(zhuǎn)變,有可能數(shù)據(jù)庫就不一定是關系型數(shù)據(jù)庫了,也許是 NoSQL 數(shù)據(jù)庫或者大數(shù)據(jù)平臺。數(shù)據(jù)庫的設計也不一定遵循 3NF(第三范式)了,可能會增加更多的冗余,甚至是寬表。
數(shù)據(jù)庫設計在發(fā)生劇烈的變化,但唯一不變的是領域?qū)ο蟆_@樣,當系統(tǒng)在大數(shù)據(jù)轉(zhuǎn)型時,可以保證業(yè)務代碼不變,變化的是數(shù)據(jù)訪問層(DAO)。這將使得日后大數(shù)據(jù)轉(zhuǎn)型的成本更低,讓我們更快地跟上技術快速發(fā)展的腳步。
二、領域模型的設計
此外,這里有個有趣的問題值得探討:領域模型的設計到底是誰的職責,是需求分析人員還是設計開發(fā)人員?我認為,它是兩個角色相互協(xié)作的產(chǎn)物。而未來敏捷開發(fā)的組織形成,團隊將更加扁平化。過去是需求分析人員做需求分析,然后交給設計人員設計開發(fā),這種方式就使得軟件設計質(zhì)量低下而結構臃腫。未來“大前端”的思想將支持更多設計開發(fā)人員直接參與需求分析,實現(xiàn)從需求分析到設計開發(fā)的一體化組織形式。這樣,領域模型就成為了設計開發(fā)人員快速理解需求的利器。
總之,**DDD 的數(shù)據(jù)庫設計實際上已經(jīng)變成了:以領域模型為核心,如何將領域模型轉(zhuǎn)換成數(shù)據(jù)庫設計的過程。**那么怎樣進行轉(zhuǎn)換呢?在領域模型中是一個一個的類,而在數(shù)據(jù)庫設計中是一個一個的表,因此就是將類轉(zhuǎn)換成表的過程。
上圖是一個績效考核系統(tǒng)的領域模型圖,該績效考核系統(tǒng)首先進行自動考核,發(fā)現(xiàn)一批過錯,然后再給一個機會,讓過錯責任人對自己的過錯進行申辯。 這時,過錯責任人可以填寫一張申辯申請單,在申辯申請單中有多個明細,每個明細對應一個過錯行為,每個過錯行為都對應了一個過錯類型,這樣就形成了一個領域模型。
接著,要將這個領域模型轉(zhuǎn)換成數(shù)據(jù)庫設計,怎么做呢?很顯然,領域模型中的一個類可以轉(zhuǎn)換成數(shù)據(jù)庫中的一個表,類中的屬性可以轉(zhuǎn)換成表中的字段。但這里的關鍵是如何處理類與類之間的關系,如何轉(zhuǎn)換成表與表之間的關系。這時候,就有 5 種類型的關系需要轉(zhuǎn)換,即傳統(tǒng)的 4 種關系 + 繼承關系。
三、傳統(tǒng)的 4 種關系
傳統(tǒng)的關系包含一對一、多對一、一對多、多對多這 4 種,它們既存在于類與類之間,又存在于表與表之間,所以可以直接進行轉(zhuǎn)換。
1、一對一關系
在以上案例中,“申辯申請單明細”與“過錯行為”就是一對“一對一”關系。在該關系中,一個“申辯申請單明細”必須要對應一個“過錯行為”,沒有一個“過錯行為”的對應就不能成為一個“申辯申請單明細”。這種約束在數(shù)據(jù)庫設計時,可以通過外鍵來實現(xiàn)。但是,一對一關系還有另外一個約束,那就是一個“過錯行為”最多只能有一個“申辯申請單明細”與之對應。
也就是說,一個“過錯行為”可以沒有“申辯申請單明細”與之對應,但如果有,最多只能有一個“申辯申請單明細”與之對應,這個約束暗含的是一種唯一性的約束。因此,將過錯行為表中的主鍵,作為申辯申請單明細表的外鍵,并將該字段升級為申辯申請單明細表的主鍵。
2、多對一關系
是日常的分析設計中最常見的一種關系。在以上案例中,一個過錯行為對應一個稅務人員、一個納稅人與一個過錯類型;同時,一個稅務人員,或納稅人,或過錯類型,都可以對應多個過錯行為。它們就形成了“多對一”關系。在數(shù)據(jù)庫設計時,通過外鍵就可以建立這種“多對一”關系。因此,我們進行了如下數(shù)據(jù)庫的設計:
多對一關系在數(shù)據(jù)庫設計上比較簡單,然而落實到程序設計時,需要好好探討一下。 比如,以上案例,在按照這樣的方式設計以后,在查詢時往往需要在查詢過錯行為的同時,顯示它們對應的稅務人員、納稅人與過錯類型。 這時,以往的設計是增加一個 join 語句。 然而,這樣的設計在隨著數(shù)據(jù)量不斷增大時,查詢性能將受到極大的影響。
也就是說,join 操作往往是關系型數(shù)據(jù)庫在面對大數(shù)據(jù)時最大的瓶頸之一。因此,一個更好的方案就是先查詢過錯行為表,分頁,然后再補填當前頁的其他關聯(lián)信息。這時,就需要在“過錯行為”這個值對象中通過屬性變量,增加對稅務人員、納稅人與過錯類型等信息的引用。
3、一對多關系
該關系往往表達的是一種主-子表的關系。譬如,以上案例中的“申辯申請單”與“申辯申請單明細”就是一對“一對多”關系。除此之外,訂單與訂單明細、表單與表單明細,都是一對多關系。一對多關系在數(shù)據(jù)庫設計上比較簡單,就是在子表中增加一個外鍵去引用主表中的主鍵。比如本案例中,申辯申請單明細表通過一個外鍵去引用申辯申請單表中的主鍵,如下圖所示。
除此之外,在程序的值對象設計時,主對象中也應當有一個集合的屬性變量去引用子對象。 如本例中,在“申辯申請單”值對象中有一個集合屬性去引用“申辯申請單明細”。 這樣,當通過申辯申請單號查找到某個申辯申請單時,同時就可以獲得它的所有申辯申請單明細,如下代碼所示:
public class Sbsqd {
private Set sbsqdMxes;
public void setSbsqdMxes(Set sbsqdMxes){
this.sbsqdMxes = sbsqdMxes;
}
public Set getSbsqdMxes(){
return this.sbsqdMxes;
}
……
} 4、多對多關系
比較典型的例子就是“用戶角色”與“功能權限”。一個“用戶角色”可以申請多個“功能權限”;而一個“功能權限”又可以分配給多個“用戶角色”使用,這樣就形成了一個“多對多”關系。這種多對多關系在對象設計時,可以通過一個“功能-角色關聯(lián)類”來詳細描述。因此,在數(shù)據(jù)庫設計時就可以添加一個“角色功能關聯(lián)表”,而該表的主鍵就是關系雙方的主鍵進行的組合,形成的聯(lián)合主鍵,如下圖所示:
以上是領域模型和數(shù)據(jù)庫都有的 4 種關系。 因此,在數(shù)據(jù)庫設計時,直接將相應的關系轉(zhuǎn)換成數(shù)據(jù)庫設計就可以了。 同時,在數(shù)據(jù)庫設計時還要將它們進一步細化。 如在領域模型中,不論對象還是屬性,在命名時都采用中文,這樣有利于溝通與理解。 但到了數(shù)據(jù)庫設計時,就要將它們細化為英文命名,或者漢語拼音首字母,同時還要確定它們的字段類型與是否為空等其他屬性。
四、繼承關系的 3 種設計
第 5 種關系就不太一樣了:繼承關系是在領域模型設計中有,但在數(shù)據(jù)庫設計中卻沒有。如何將領域模型中的繼承關系轉(zhuǎn)換成數(shù)據(jù)庫設計呢?有 3 種方案可以選擇。
1、繼承關系的第一種方案
首先,看看以上案例。“執(zhí)法行為”通過繼承分為“正確行為”和“過錯行為”。如果這種繼承關系的子類不多(一般就 2 ~ 3 個),并且每個子類的個性化字段也不多(3 個以內(nèi))的話,則可以使用一個表來記錄整個繼承關系。在這個表的中間有一個標識字段,標識表中的每條記錄到底是哪個子類,這個字段的前面部分羅列的是父類的字段,后面依次羅列各個子類的個性化字段。
采用這個方案的優(yōu)點是簡單,整個繼承關系的數(shù)據(jù)全部都保存在這個表里。 但是,它會造成“表稀疏”。 在該案例中,如果是一條“正確行為”的記錄,則字段“過錯類型”與“扣分”永遠為空; 如果是一條“過錯行為”的記錄,則字段“加分”永遠為空。 假如這個繼承關系中各子類的個性化字段很多,就會造成該表中出現(xiàn)大量字段為空,稱為“表稀疏”。 在關系型數(shù)據(jù)庫中,為空的字段是要占用空間的。 因此,這種“表稀疏”既會浪費大量存儲空間,又會影響查詢速度,是需要極力避免的。 所以,當子類比較多,或者子類個性化字段多的情況是不適合該方案(第一種方案)的。
2、繼承關系的第二種方案
如果執(zhí)法行為按照考核指標的類型進行繼承,分為“考核指標1”“考核指標2”“考核指標3”……如下圖所示:
并且每個子類都有很多的個性化字段,則采用前面那個方案就不合適了。 這時,用另外兩個方案進行數(shù)據(jù)庫設計。 其中一個方案是將每個子類都對應到一個表,有幾個子類就有幾個表,這些表共用一個主鍵,即這幾個表的主鍵生成器是一個,某個主鍵值只能存在于某一個表中,不能存在于多個表中。 每個表的前面是父類的字段,后面羅列各個子類的字段,如下圖所示:
如果業(yè)務需求是在前端查詢時,每次只能查詢某一個指標,那么采用這種方案就能將每次查詢落到某一個表中,方案就最合適。 但如果業(yè)務需求是要查詢某個過錯責任人涉及的所有指標,則采用這種方案就必須要在所有的表中進行掃描,那么查詢效率就比較低,并不適用。
3、繼承關系的第三種方案
如果業(yè)務需求是要查詢某個過錯責任人涉及的所有指標,則更適合采用以下方案,將父類做成一個表,各個子類分別對應各自的表(如圖所示)。這樣,當需要查詢某個過錯責任人涉及的所有指標時,只需要查詢父類的表就可以了。如果要查看某條記錄的詳細信息,再根據(jù)主鍵與類型字段,查詢相應子類的個性化字段。這樣,這種方案就可以完美實現(xiàn)該業(yè)務需求。
綜上所述,將領域模型中的繼承關系轉(zhuǎn)換成數(shù)據(jù)庫設計有 3 種方案,并且每個方案都有各自的優(yōu)缺點。 因此,需要根據(jù)業(yè)務場景的特點與需求去評估,選擇哪個方案更適用。
五、NoSQL 數(shù)據(jù)庫的設計
前面我們講的數(shù)據(jù)庫設計,還是基于傳統(tǒng)的關系型數(shù)據(jù)庫、基于第三范式的數(shù)據(jù)庫設計。但是,隨著互聯(lián)網(wǎng)高并發(fā)與分布式技術的發(fā)展,另一種全新的數(shù)據(jù)庫類型孕育而生,那就是NoSQL 數(shù)據(jù)庫。正是由于互聯(lián)網(wǎng)應用帶來的高并發(fā)壓力,采用關系型數(shù)據(jù)庫進行集中式部署不能滿足這種高并發(fā)的壓力,才使得分布式 NoSQL 數(shù)據(jù)庫得到快速發(fā)展。
也正因為如此,NoSQL 數(shù)據(jù)庫與關系型數(shù)據(jù)庫的設計套路是完全不同的。關系型數(shù)據(jù)庫的設計是遵循第三范式進行的,它使得數(shù)據(jù)庫能夠大幅度降低冗余,但又從另一個角度使得數(shù)據(jù)庫查詢需要頻繁使用 join 操作,在高并發(fā)場景下性能低下。
所以,NoSQL 數(shù)據(jù)庫的設計思想就是盡量干掉 join 操作,即將需要 join 的查詢在寫入數(shù)據(jù)庫表前先進行 join 操作,然后直接寫到一張單表中進行分布式存儲,這張表稱為“寬表”。這樣,在面對海量數(shù)據(jù)進行查詢時,就不需要再進行 join 操作,直接在這個單表中查詢。同時,因為 NoSQL 數(shù)據(jù)庫自身的特點,使得它在存儲為空的字段時不占用空間,不擔心“表稀疏”,不影響查詢性能。
因此,NoSQL 數(shù)據(jù)庫在設計時的套路就是,盡量在單表中存儲更多的字段,只要避免數(shù)據(jù)查詢中的 join 操作,即使出現(xiàn)大量為空的字段也無所謂了。
增值稅發(fā)票票樣圖
正因為 NoSQL 數(shù)據(jù)庫在設計上有以上特點,因此將領域模型轉(zhuǎn)換成 NoSQL 數(shù)據(jù)庫時,設計就完全不一樣了。比如,這樣一張增值稅發(fā)票,如上圖所示,在數(shù)據(jù)庫設計時就需要分為發(fā)票信息表、發(fā)票明細表與納稅人表,而在查詢時需要進行 4 次 join 才能完成查詢。但在 NoSQL 數(shù)據(jù)庫設計時,將其設計成這樣一張表:
{ _id: ObjectId(7df78ad8902c)
fpdm: '3700134140', fphm: '02309723‘,
kprq: '2016-1-25 9:22:45',
je: 70451.28, se: 11976.72,
gfnsr: {
nsrsbh: '370112582247803',
nsrmc:'聯(lián)通華盛通信有限公司濟南分公司',…
},
xfnsr: {
nsrsbh: '370112575587500',
nsrmc:'聯(lián)通華盛通信有限公司濟南分公司',…
},
spmx: [
{ qdbz:'00', wp_mc:'藍牙耳機 車語者S1 藍牙耳機', sl:2, dj:68.37,… },
{ qdbz:'00', wp_mc:'車載充電器 新在線', sl:1, dj:11.11,… },
{ qdbz:'00', wp_mc:'保護殼 非尼膜屬 iPhone6 電鍍殼', sl:1, dj:24,… }
]
}在該案例中,對于“一對一”和“多對一”關系,在發(fā)票信息表中通過一個類型為“對象”的字段來存儲,比如“購方納稅人(gfnsr)”與“銷方納稅人(xfnsr)”字段。對于“一對多”和“多對多”關系,通過一個類型為“對象數(shù)組”的字段來存儲,如“商品明細(spmx)”字段。在這樣一個發(fā)票信息表中就可以完成對所有發(fā)票的查詢,無須再進行任何 join 操作。
同樣,采用 NoSQL 數(shù)據(jù)庫怎樣實現(xiàn)繼承關系的設計呢?由于 NoSQL 數(shù)據(jù)庫自身的特點決定了不用擔心“表稀疏”,同時要避免 join 操作,所以比較適合采用第一個方案,即將整個繼承關系放到同一張表中進行設計。這時,NoSQL 數(shù)據(jù)庫的每一條記錄可以有不一定完全相同的字段,可以設計成這樣:
{ _id: ObjectId(79878ad8902c),
name: ‘Jack’,
type: ‘parent’,
partner: ‘Elizabeth’,
children: [
{ name: ‘Tom’, gender: ‘male’ },
{ name: ‘Mary’, gender: ‘female’}
]
},
{ _id: ObjectId(79878ad8903d),
name: ‘Bob’,
type: ‘kid’,
mother: ‘Anna’,
father: ‘David’
}以上案例是一個用戶檔案表,有兩條記錄:Jack 與 Bob。但是,Jack 的類型是“家長”,因此其個性化字段是“伴侶”與“孩子”;而 Bob 的類型是“孩子”,因此他的個性化字段是“父親”與“母親”。顯然,在 NoSQL 數(shù)據(jù)庫設計時就會變得更加靈活。
六、總結
將領域模型落地到系統(tǒng)設計包含 2 部分內(nèi)容,本文演練了第一部分內(nèi)容——從 DDD 落實到數(shù)據(jù)庫設計的整個過程:傳統(tǒng)的 4 種關系可以直接轉(zhuǎn)換;繼承關系有 3 種設計方案;轉(zhuǎn)換成 NoSQL 數(shù)據(jù)庫則是完全不同的思路。
有了 DDD 的指導,可以幫助我們理清數(shù)據(jù)間的關系,以及對數(shù)據(jù)的操作。不僅如此,在未來面對大數(shù)據(jù)轉(zhuǎn)型時更加從容。
網(wǎng)站欄目:擔保這次能看懂!DDD落地數(shù)據(jù)庫設計實戰(zhàn)
網(wǎng)頁URL:http://m.fisionsoft.com.cn/article/dpiehhd.html


咨詢
建站咨詢
