咱們都知道,不少業務系統都是基於 MVC
三層架構來開發的。實際上,更確切點講,這是一種基於貧血模型的 MVC
三層架構開發模式。前端
雖然這種開發模式已經成爲標準的 Web
項目的開發模式,但它卻違反了面向對象編程風格,是一種不折不扣的面向過程的編程風格,所以而被有些人稱爲反模式(anti-pattern
)。特別是領域驅動設計(Domain Driven Design
,簡稱 DDD
)盛行以後,這種基於貧血模型的傳統的開發模式就更加被人詬病。而基於充血模型的 DDD
開發模式愈來愈被人提倡。java
基於上面的描述,咱們先搞清楚下面幾個問題:程序員
OOP
?OOP
,那又爲何如此流行?DDD
開發模式?對於大部分的後端開發工程師來講,MVC
三層架構都不會陌生。數據庫
MVC
三層架構中的 M
表示 Model
,V
表示 View
,C
表示 Controller
。它將整個項目分爲三層:展現層、邏輯層、數據層。MVC
三層開發架構是一個比較籠統的分層方式,落實到具體的開發層面,不少項目也並不會 100%
聽從 MVC 固定的分層方式,而是會根據具體的項目需求,作適當的調整。編程
好比,如今不少 Web
或者 App
項目都是先後端分離的,後端負責暴露接口給前端調用。這種狀況下,咱們通常就將後端項目分爲 Repository
層、Service
層、Controller
層。其中,Repository
層負責數據訪問,Service
層負責業務邏輯,Controller
層負責暴露接口。固然,這只是其中一種分層和命名方式。不一樣的項目、不一樣的團隊,可能會對此有所調整。不過,萬變不離其宗,只要是依賴數據庫開發的 Web
項目,基本的分層思路都大差不差。後端
再來看一下,什麼是貧血模型?數據結構
目前幾乎全部的業務後端系統,都是基於貧血模型的。舉一個簡單的例子來解釋一下。架構
////////// Controller+VO(View Object) ////////// public class UserController { private UserService userService; //經過構造函數或者IOC框架注入 public UserVo getUserById(Long userId) { UserBo userBo = userService.getUser(userId); UserVo userVo = [...convert userBo to userVo...]; return userVo; } } public class UserVo {//省略其餘屬性、get/set/construct方法 private Long id; private String name; private String cellphone; } ////////// Service+BO(Business Object) ////////// public class UserService { private UserRepository userRepository; //經過構造函數或者IOC框架注入 public UserBo getUserById(Long userId) { UserEntity userEntity = userRepository.getUserById(userId); UserBo userBo = [...convert userEntity to userBo...]; return userBo; } } public class UserBo {//省略其餘屬性、get/set/construct方法 private Long id; private String name; private String cellphone; } ////////// Repository+Entity ////////// public class UserRepository { public UserEntity getUserById(Long userId) { //... } } public class UserEntity {//省略其餘屬性、get/set/construct方法 private Long id; private String name; private String cellphone; }
平時開發 Web
後端項目的時候,基本上都是這麼組織代碼的。其中,UserEntity
和 UserRepository
組成了數據訪問層,UserBo
和 UserService
組成了業務邏輯層,UserVo
和 UserController
在這裏屬於接口層。框架
從代碼中能夠發現,UserBo
是一個純粹的數據結構,只包含數據,不包含任何業務邏輯。業務邏輯集中在 UserService
中。咱們經過 UserService
來操做 UserBo
。換句話說,Service
層的數據和業務邏輯,被分割爲 BO
和 Service
兩個類中。像 UserBo
這樣,只包含數據,不包含業務邏輯的類,就叫做貧血模型(Anemic Domain Model
)。同理,UserEntity
、UserVo
都是基於貧血模型設計的。這種貧血模型將數據與操做分離,破壞了面向對象的封裝特性,是一種典型的面向過程的編程風格。前後端分離
首先,咱們先來看一下,什麼是充血模型?
在貧血模型中,數據和業務邏輯被分割到不一樣的類中。充血模型(Rich Domain Model
)正好相反,數據和對應的業務邏輯被封裝到同一個類中。所以,這種充血模型知足面向對象的封裝特性,是典型的面向對象編程風格。
接下來,再來看一下,什麼是領域驅動設計?
領域驅動設計,即 DDD
,主要是用來指導如何解耦業務系統,劃分業務模塊,定義業務領域模型及其交互。領域驅動設計這個概念並不新穎,早在 2004
年就被提出了,到如今已經有十幾年的歷史了。不過,它被大衆熟知,仍是基於另外一個概念的興起,那就是微服務。
除了監控、調用鏈追蹤、API
網關等服務治理系統的開發以外,微服務還有另一個更加劇要的工做,那就是針對公司的業務,合理地作微服務拆分。而領域驅動設計剛好就是用來指導劃分服務的。因此,微服務加速了領域驅動設計的盛行。
領域驅動設計有點兒相似敏捷開發、SOA
、PAAS
等概念,聽起來很高大上,但實際上只值「五分錢」。即使你沒有據說過領域驅動設計,對這個概念一無所知,只要你是在開發業務系統,也或多或少都在使用它。作好領域驅動設計的關鍵是,看你對本身所作業務的熟悉程度,而並非對領域驅動設計這個概念自己的掌握程度。即使你對領域驅動搞得再清楚,可是對業務不熟悉,也並不必定能作出合理的領域設計。因此,不要把領域驅動設計當銀彈,不要花太多的時間去過分地研究它。
實際上,基於充血模型的 DDD
開發模式實現的代碼,也是按照 MVC
三層架構分層的。Controller
層仍是負責暴露接口,Repository
層仍是負責數據存取,Service
層負責核心業務邏輯。它跟基於貧血模型的傳統開發模式的區別主要在 Service
層。
在基於貧血模型的傳統開發模式中,Service
層包含 Service
類和 BO
類兩部分,BO
是貧血模型,只包含數據,不包含具體的業務邏輯。業務邏輯集中在 Service
類中。在基於充血模型的 DDD
開發模式中,Service
層包含 Service
類和 Domain
類兩部分。Domain
就至關於貧血模型中的 BO
。不過,Domain
與 BO
的區別在於它是基於充血模型開發的,既包含數據,也包含業務邏輯。而 Service
類變得很是單薄。總結一下的話就是,基於貧血模型的傳統的開發模式,重 Service
輕 BO
;基於充血模型的 DDD
開發模式,輕 Service
重 Domain
。
基於貧血模型的傳統開發模式,將數據與業務邏輯分離,違反了 OOP
的封裝特性,其實是一種面向過程的編程風格。可是,如今幾乎全部的 Web
項目,都是基於這種貧血模型的開發模式,甚至連 Java Spring
框架的官方 demo
,都是按照這種開發模式來編寫的。
面向過程編程風格有種種弊端,好比,數據和操做分離以後,數據自己的操做就不受限制了。任何代碼均可以隨意修改數據。既然基於貧血模型的這種傳統開發模式是面向過程編程風格的,那它又爲何會被廣大程序員所接受呢?
第一點緣由是,大部分狀況下,咱們開發的系統業務可能都比較簡單,簡單到就是基於 SQL
的 CRUD
操做,因此,咱們根本不須要動腦子精心設計充血模型,貧血模型就足以應付這種簡單業務的開發工做。除此以外,由於業務比較簡單,即使咱們使用充血模型,那模型自己包含的業務邏輯也並不會不少,設計出來的領域模型也會比較單薄,跟貧血模型差很少,沒有太大意義。
第二點緣由是,充血模型的設計要比貧血模型更加有難度。由於充血模型是一種面向對象的編程風格。咱們從一開始就要設計好針對數據要暴露哪些操做,定義哪些業務邏輯。而不是像貧血模型那樣,咱們只須要定義數據,以後有什麼功能開發需求,咱們就在 Service
層定義什麼操做,不須要事先作太多設計。
第三點緣由是,思惟已固化,轉型有成本。基於貧血模型的傳統開發模式經歷了這麼多年,已經深得人心、習覺得常。你隨便問一個旁邊的大齡同事,基本上他過往參與的全部 Web
項目應該都是基於這個開發模式的,並且也沒有出過啥大問題。若是轉向用充血模型、領域驅動設計,那勢必有必定的學習成本、轉型成本。不少人在沒有遇到開發痛點的狀況下,是不肯意作這件事情的。
基於貧血模型的傳統的開發模式,比較適合業務比較簡單的系統開發。相對應的,基於充血模型的 DDD
開發模式,更適合業務複雜的系統開發。好比,包含各類利息計算模型、還款模型等複雜業務的金融系統。
這兩種開發模式,落實到代碼層面,區別不就是一個將業務邏輯放到 Service
類中,一個將業務邏輯放到 Domain
領域模型中嗎?爲何基於貧血模型的傳統開發模式,就不能應對複雜業務系統的開發?而基於充血模型的 DDD
開發模式就能夠呢?
實際上,除了咱們能看到的代碼層面的區別以外(一個業務邏輯放到 Service
層,一個放到領域模型中),還有一個很是重要的區別,那就是兩種不一樣的開發模式會致使不一樣的開發流程。基於充血模型的 DDD
開發模式的開發流程,在應對複雜業務系統的開發的時候更加有優點。爲何這麼說呢?先來回憶一下,咱們平時基於貧血模型的傳統的開發模式,都是怎麼實現一個功能需求的。
不誇張地講,咱們平時的開發,大部分都是 SQL
驅動(SQL-Driven
)的開發模式。咱們接到一個後端接口的開發需求的時候,就去看接口須要的數據對應到數據庫中,須要哪張表或者哪幾張表,而後思考如何編寫 SQL
語句來獲取數據。以後就是定義 Entity
、BO
、VO
,而後模板式地往對應的 Repository
、Service
、Controller
類中添加代碼。
業務邏輯包裹在一個大的 SQL
語句中,而 Service
層能夠作的事情不多。SQL
都是針對特定的業務功能編寫的,複用性差。當我要開發另外一個業務功能的時候,只能從新寫個知足新需求的 SQL
語句,這就可能致使各類長得差很少、區別很小的 SQL
語句滿天飛。
因此,在這個過程當中,不多有人會應用領域模型、OOP
的概念,也不多有代碼複用意識。對於簡單業務系統來講,這種開發方式問題不大。但對於複雜業務系統的開發來講,這樣的開發方式會讓代碼愈來愈混亂,最終致使沒法維護。
若是咱們在項目中,應用基於充血模型的 DDD
的開發模式,那對應的開發流程就徹底不同了。在這種開發模式下,咱們須要事先理清楚全部的業務,定義領域模型所包含的屬性和方法。領域模型至關於可複用的業務中間層。新功能需求的開發,都基於以前定義好的這些領域模型來完成。
越複雜的系統,對代碼的複用性、易維護性要求就越高,咱們就越應該花更多的時間和精力在前期設計上。而基於充血模型的 DDD
開發模式,正好須要咱們前期作大量的業務調研、領域模型設計,因此它更加適合這種複雜系統的開發。
平時作 Web
項目的業務開發,大部分都是基於貧血模型的 MVC
三層架構,這裏把它稱爲傳統的開發模式。之因此稱之爲「傳統」,是相對於新興的基於充血模型的 DDD
開發模式來講的。基於貧血模型的傳統開發模式,是典型的面向過程的編程風格。相反,基於充血模型的 DDD
開發模式,是典型的面向對象的編程風格。
不過,DDD
也並不是銀彈。對於業務不復雜的系統開發來講,基於貧血模型的傳統開發模式簡單夠用,基於充血模型的 DDD
開發模式有點大材小用,沒法發揮做用。相反,對於業務複雜的系統開發來講,基於充血模型的 DDD
開發模式,由於前期須要在設計上投入更多時間和精力,來提升代碼的複用性和可維護性,因此相比基於貧血模型的開發模式,更加有優點。
UserEntity
、UserBo
、UserVo
包含的字段都差很少,是否能夠合併爲一個類呢?參考:實戰一(上):業務開發經常使用的基於貧血模型的MVC架構違背OOP嗎?