貧血模型和充血模型

簡述:

一、貧血模型

所謂貧血模型,是指Model 中,僅包含狀態(tài)(屬性),不包含行為(方法),采用這種設(shè)計時,需要分離出DB層,專門用于數(shù)據(jù)庫操作。

二、充血模型

Model 中既包括狀態(tài),又包括行為,是最符合面向?qū)ο蟮脑O(shè)計方式。

什么是基于充血模型的 DDD 開發(fā)模式?

在貧血模型中,數(shù)據(jù)和業(yè)務邏輯被分割到不同的類中。充血模型(Rich Domain Model)正好相反,數(shù)據(jù)和對應的業(yè)務邏輯被封裝到同一個類中。因此,這種充血模型滿足面向?qū)ο蟮姆庋b特性,是典型的面向?qū)ο缶幊田L格。

領(lǐng)域驅(qū)動設(shè)計,即 DDD,主要是用來指導如何解耦業(yè)務系統(tǒng),劃分業(yè)務模塊,定義業(yè)務領(lǐng)域模型及其交互。

在基于充血模型的 DDD 開發(fā)模式中,Service 層包含 Service 類和 Domain 類兩部分。Domain 就相當于貧血模型中的 BO。不過,Domain 與 BO 的區(qū)別在于它是基于充血模型開發(fā)的,既包含數(shù)據(jù),也包含業(yè)務邏輯。而 Service 類變得非常單薄。

基于充血模型的 DDD 開發(fā)模式,跟基于貧血模型的傳統(tǒng)開發(fā)模式的主要區(qū)別就在 Service 層,Controller 層和 Repository 層的代碼基本上相同。

總結(jié)一下的話就是,基于貧血模型的傳統(tǒng)的開發(fā)模式,重 Service 輕 BO;基于充血模型的 DDD 開發(fā)模式,輕 Service 重Domain。

為什么基于貧血模型的傳統(tǒng)開發(fā)模式如此受歡迎?

1、大部分情況下,我們開發(fā)的系統(tǒng)業(yè)務可能都比較簡單,簡單到就是基于SQL 的 CRUD 操作,所以,我們根本不需要動腦子精心設(shè)計充血模型,貧血模型就足以應付這種簡單業(yè)務的開發(fā)工作。
2、充血模型的設(shè)計要比貧血模型更加有難度。因為充血模型是一種面向?qū)ο蟮木幊田L格。我們從一開始就要設(shè)計好針對數(shù)據(jù)要暴露哪些操作,定義哪些業(yè)務邏輯。而不是像貧血模型那樣,我們只需要定義數(shù)據(jù),之后有什么功能開發(fā)需求,我們就在 Service 層定義什么操作,不需要事先做太多設(shè)計。
3、思維已固化,轉(zhuǎn)型有成本。

什么項目應該考慮使用基于充血模型的 DDD 開發(fā)模式?

基于貧血模型的傳統(tǒng)的開發(fā)模式,比較適合業(yè)務比較簡單的系統(tǒng)開發(fā)。相對應的,基于充血模型的 DDD 開發(fā)模式,更適合業(yè)務復雜的系統(tǒng)開發(fā)。比如,包含各種利息計算模型、還款模型等復雜業(yè)務的金融系統(tǒng)。

你可能會有一些疑問,這兩種開發(fā)模式,落實到代碼層面,區(qū)別不就是一個將業(yè)務邏輯放到Service 類中,一個將業(yè)務邏輯放到 Domain 領(lǐng)域模型中嗎?為什么基于貧血模型的傳統(tǒng)開發(fā)模式,就不能應對復雜業(yè)務系統(tǒng)的開發(fā)?而基于充血模型的 DDD 開發(fā)模式就可以呢?

答案:除了代碼層面,兩種不同的開發(fā)模式會導致不同的開發(fā)流程。

我們平時的開發(fā),大部分都是 SQL 驅(qū)動(SQL-Driven)的開發(fā)模式。我們接到一個后端接口的開發(fā)需求的時候,就去看接口需要的數(shù)據(jù)對應到數(shù)據(jù)庫中,需要哪張表或者哪幾張表,然后思考如何編寫 SQL 語句來獲取數(shù)據(jù)。之后就是定義 Entity、BO、VO,然后模板式地往對應的 Repository、Service、Controller 類中添加代碼。

業(yè)務邏輯包裹在一個大的 SQL 語句中,而 Service 層可以做的事情很少。SQL 都是針對特定的業(yè)務功能編寫的,復用性差。當我要開發(fā)另一個業(yè)務功能的時候,只能重新寫個滿足新需求的 SQL 語句,這就可能導致各種長得差不多、區(qū)別很小的 SQL 語句滿天飛。

所以,在這個過程中,很少有人會應用領(lǐng)域模型、OOP 的概念,也很少有代碼復用意識。對于簡單業(yè)務系統(tǒng)來說,這種開發(fā)方式問題不大。但對于復雜業(yè)務系統(tǒng)的開發(fā)來說,這樣的開發(fā)方式會讓代碼越來越混亂,最終導致無法維護。

如果我們在項目中,應用基于充血模型的 DDD 的開發(fā)模式,那對應的開發(fā)流程就完全不一樣了。在這種開發(fā)模式下,我們需要事先理清楚所有的業(yè)務,定義領(lǐng)域模型所包含的屬性和方法。領(lǐng)域模型相當于可復用的業(yè)務中間層。新功能需求的開發(fā),都基于之前定義好的這些領(lǐng)域模型來完成。

我們知道,越復雜的系統(tǒng),對代碼的復用性、易維護性要求就越高,我們就越應該花更多的時間和精力在前期設(shè)計上。而基于充血模型的 DDD 開發(fā)模式,正好需要我們前期做大量的業(yè)務調(diào)研、領(lǐng)域模型設(shè)計,所以它更加適合這種復雜系統(tǒng)的開發(fā)。

在基于充血模型的 DDD 開發(fā)模式中,將業(yè)務邏輯移動到Domain 中,Service 類變得很薄,但在我們的代碼設(shè)計與實現(xiàn)中,并沒有完全將Service 類去掉,這是為什么?或者說,Service 類在這種情況下?lián)數(shù)穆氊熓鞘裁矗磕男┕δ苓壿嫊诺?Service 類中?
1、Service 類負責與 Repository 交流。在我的設(shè)計與代碼實現(xiàn)中,VirtualWalletService類負責與 Repository 層打交道,調(diào)用 Respository 類的方法,獲取數(shù)據(jù)庫中的數(shù)據(jù),轉(zhuǎn)化成領(lǐng)域模型 VirtualWallet,然后由領(lǐng)域模型 VirtualWallet 來完成業(yè)務邏輯,最后調(diào)用Repository 類的方法,將數(shù)據(jù)存回數(shù)據(jù)庫。

之所以讓 VirtualWalletService 類與 Repository 打交道,而不是讓領(lǐng)域模型 VirtualWallet 與 Repository 打交道,那是因為我們想保持領(lǐng)域模型的獨立性,不與任何其他層的代碼(Repository 層的代碼)或開發(fā)框架(比如 Spring、MyBatis)耦合在一起,將流程性的代碼邏輯(比如從 DB 中取數(shù)據(jù)、映射數(shù)據(jù))與領(lǐng)域模型的業(yè)務邏輯解耦,讓領(lǐng)域模型更加可復用。

2、Service 類負責跨領(lǐng)域模型的業(yè)務聚合功能。VirtualWalletService 類中的 transfer() 轉(zhuǎn)賬函數(shù)會涉及兩個錢包的操作,因此這部分業(yè)務邏輯無法放到 VirtualWallet 類中,所以,我們暫且把轉(zhuǎn)賬業(yè)務放到 VirtualWalletService 類中了。當然,雖然功能演進,使得轉(zhuǎn)賬業(yè)務變得復雜起來之后,我們也可以將轉(zhuǎn)賬業(yè)務抽取出來,設(shè)計成一個獨立的領(lǐng)域模型。

3、Service 類負責一些非功能性及與三方系統(tǒng)交互的工作。比如冪等、事務、發(fā)郵件、發(fā)消息、記錄日志、調(diào)用其他系統(tǒng)的 RPC 接口等,都可以放到 Service 類中。

在基于充血模型的 DDD 開發(fā)模式中,盡管 Service 層被改造成了充血模型,但是 Controller 層和 Repository 層還是貧血模型,是否有必要也進行充血領(lǐng)域建模呢?
答案是沒有必要。Controller 層主要負責接口的暴露,Repository 層主要負責與數(shù)據(jù)庫打交道,這兩層包含的業(yè)務邏輯并不多,前面我們也提到了,如果業(yè)務邏輯比較簡單,就沒必要做充血建模,即便設(shè)計成充血模型,類也非常單薄,看起來也很奇怪。

盡管這樣的設(shè)計是一種面向過程的編程風格,但我們只要控制好面向過程編程風格的副作用,照樣可以開發(fā)出優(yōu)秀的軟件。那這里的副作用怎么控制呢?

就拿 Repository 的 Entity 來說,即便它被設(shè)計成貧血模型,違反面相對象編程的封裝特性,有被任意代碼修改數(shù)據(jù)的風險,但Entity 的生命周期是有限的。一般來講,我們把它傳遞到 Service 層之后,就會轉(zhuǎn)化成 BO 或者 Domain 來繼續(xù)后面的業(yè)務邏輯。Entity 的生命周期到此就結(jié)束了,所以也并不會被到處任意修改。

我們再來說說 Controller 層的 VO。實際上 VO 是一種 DTO(Data Transfer Object,數(shù)據(jù)傳輸對象)。它主要是作為接口的數(shù)據(jù)傳輸承載體,將數(shù)據(jù)發(fā)送給其他系統(tǒng)。從功能上來講,它理應不包含業(yè)務邏輯、只包含數(shù)據(jù)。所以,我們將它設(shè)計成貧血模型也是比較合理的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容