點贊再看,養成習慣,公衆號搜一搜【一角錢技術】關注更多原創技術文章。本文 GitHub org_hejianhui/JavaStudy 已收錄,有個人系列文章。git
最近加入到一個新的團隊,總體的框架方向是構建業務中臺,劃分子域、上下文、需求結構化和能力可配置,是經過領域驅動,從總體上劃分業務中臺的領域,進而劃分出業務中臺的具體能力中心,。本篇文章開始,將會結合本身的實際經驗,聊一聊DDD(領域驅動設計)的應用。這裏咱們主要聊如下咱們常常會用的的領域模型:VO、DTO、DO、PO。github
領域模型中的實體類分爲四種模型:VO、DTO、DO、PO,各類實體類用於不一樣業務層次間的交互,並會在層次內實現實體類之間的轉化。數據庫
業務分層爲:視圖層(VIEW+ACTION)、服務層(SERVICE)、持久層(DAO),相應各層間實體的傳遞以下: 設計模式
用於展現層,它的做用是把某個指定頁面(或組件)的全部數據封裝起來。markdown
這個概念來源於J2EE的設計模式,原來的目的是爲了EJB的分佈式應用提供粗粒度的數據實體,以減小分佈式調用的次數,從而提升分佈式調用的性能和下降網絡負載,可是這裏,主要用於展現層與服務層之間的數據傳輸對象。網絡
好比一張表有100個字段,那麼對應的DTO就有100個屬性(大多數狀況下,DTO內的數據來自多張表)。可是view層只須要顯示10個字段,沒有必要把整個PO對象傳遞到 client,這時咱們就能夠用只有這10個屬性的DTO來傳輸數據到 client,這樣也不會暴露 server 端的表結構。到達客戶端後,若是用這個對象來對應界面展現,那麼此時它的身份就轉爲 VO。session
就是從現實世界中抽象出來的有形或無形的業務實體。數據結構
它跟持久層(一般是關係型數據庫)的數據結構造成一一對應的映射關係,若是持久層是關係型數據庫,那麼,數據表中的每一個字段就對應PO的一個屬性。架構
對於以上概念的理解,可能還不能造成一種抽象化思惟,咱們經過一個時序圖創建模型來描述上述對象在三層架構應用中的位置: 框架
對於一個逆向操做,如讀取數據,也是用相似的方式轉換和傳遞。
在這裏咱們可能會問:既然 DTO 是展現層與服務層之間傳遞數據的對象,爲何還要一個 VO 呢?
是的,對於絕大部分的應用場景來講,DTO 和 VO 的屬性值基本是一致的,並且他們一般都是 POJO,所以不必畫蛇添足。但不要忘記這是實現層的思惟,對於設計層面來講,概念上仍是應該存在 VO 和 DTO,所以二者有着本質的區別,DTO 表明服務層須要接收的數據和返回的數據,而 VO 表明展現層須要顯示的數據。
用一個例子來講明可能會比較容易理解:
例如:Service 層有一個 getUser 的方法返回一個系統用戶,其中有一個屬性是 gender(性別),對於 Service 層來講,它只從語義上定義:1-男性、2-女性、0-未指定,而對於展現層來講,它可能須要用「帥哥」表明男性、「美女」表明女性、「祕密」表明未指定。
說到這裏,可能你還會反駁,在服務層直接返回「帥哥、美女」不就行嗎?對於大部分應用來講,這不是問題,但設想如下,若是需求容許客戶能夠定製風格,而不一樣的客戶端對於表現層的要求有所不一樣,那麼,問題就來了。再者,回到設計層面分析,從職責單一原則來看,服務層只負責業務,與具體的表現形式無關,所以,它返回的 DTO,不該該出現與表現形式的耦合。
理論歸理論,這到底仍是分析設計層面的思惟,是否在具體實現層面必須這樣作呢?一刀切的作法每每會得不償失,下面咱們具體分析應用中如何作出正確的選擇。
在上面只是用了一個簡單的例子來講 VO 與 DTO 在概念上區別,這裏咱們具體分析在應用中如何作出正確的選擇。
在如下場景中,咱們能夠考慮把 VO 與 DTO 合二爲一(注意:是實現層面):
隱退。
如下場景須要優先考慮 VO、DTO 並存:
首先是概念上的區別,DTO 是展現層和服務層之間的數據傳輸對象(能夠認爲是二者之間的協議),而 DO 是對現實世界各類業務角色的抽象,這就引出了二者在數據上的區別。
例如:UserInfo 和 User ,對於一個 getUser 方法來講,本質上它永遠不該該返回用戶的密碼,所以 UserInfo 至少比 User 少一個 password 的數據。而在領域驅動設計中,DO不是簡單的POJO,它具備領域業務邏輯。
從上面會反向問題:既然 getUser 方法返回的 UserInfo 不該該包含 password,那麼就不該該存在 password 這個屬性定義,可是若是同時有一個 createUser 的防腐,傳入的UserInfo須要包含用戶的 password,怎麼辦?
在設計層面,展現層向服務層傳遞的 DTO 與 服務層返回給展現層的 DTO 在概念上是不一樣的,但在實現層面,咱們一般不多會這樣作(定義兩個 UserInfo,甚至更多),由於這樣作並不見得很明智,咱們徹底能夠設計一個徹底兼容的DTO,在服務層接收數據的時候,不該該由展現層設置的屬性(如訂單的蹤影應該由其單價、數量、折扣等決定),不管展現層是否設置,服務層都一律忽略,而在服務層返回數據時,不應返回的數據(如用戶密碼),就不設置對應的屬性。
對於DO來講,還有一點須要說明:爲何不在服務層中直接返回 DO 呢?這樣能夠省去 DTO 的編碼和轉換工做,緣由以下:
對於DTO來講,也有一點必須進行說明,就是DTO應該是一個「扁平的二維對象」舉個例子:
DO 和 PO 在絕大部分狀況下是一一對應的,PO是隻含有 get/set 方法的POJO,但某些場景仍是能反映出二者在概念上存在本質區別:
這裏要特別聲明,並非全部多對多關係都沒有業務含義,這跟具體業務場景有關,例如:兩個PO之間的關係會影響具體業務,而且這種關係存在多種類型,那麼這種多對多關係也應該表現爲一個DO,又如:「角色」與「資源」之間存在多對多關係,而這種關係很明顯會表現爲一個DO——「權限」。
某些狀況下,爲了某種持久化策略或者性能的考慮,一個PO可能對應多個DO,反之亦然。例如客戶Customer有其聯繫信息Contacts,這裏是兩個一對一關係的DO,但可能出於性能的考慮(極端狀況,權做舉例),爲了減小數據庫的鏈接查詢操做,把Customer和Contacts兩個DO數據合併到一張數據表中。反過來,若是一本圖書Book,有一個屬性是封面cover,但該屬性是一副圖片的二進制數據,而某些查詢操做不但願把cover一併加載,從而減輕磁盤IO開銷,同時假設ORM框架不支持屬性級別的延遲加載,那麼就須要考慮把cover獨立到一張數據表中去,這樣就造成一個DO對應多個PO的狀況。 PO的某些屬性值對於DO沒有任何意義,這些屬性值多是爲了解決某些持久化策略而存在的數據,例如爲了實現「樂觀鎖」,PO存在一個version的屬性,這個version對於DO來講是沒有任何業務意義的,它不該該在DO中存在。同理,DO中也可能存在不須要持久化的屬性。
因爲ORM框架的功能很是強大而大行其道,並且JavaEE也推出了JPA規範,如今的業務應用開發,基本上不須要區分DO與PO,PO徹底能夠經過JPA,Hibernate Annotations/hbm隱藏在DO之中。雖然如此,但有些問題咱們還必須注意:
到目前爲止,已經比較清晰的瞭解VO、DTO、DO、PO的概念、區別和實際應用了。經過上面的詳細分析,咱們還能夠總結出一個原則:分析設計層面和實現層面徹底是兩個獨立的層面,即便實現層面經過某種技術手段能夠把兩個徹底獨立的概念合二爲一,在分析設計層面,咱們仍然(至少在頭腦中)須要把概念上獨立的東西清晰的區分開來,這個原則對於作好分析設計很是重要(工具越先進,每每會讓咱們越麻木)。
文章持續更新,能夠公衆號搜一搜「 一角錢技術 」第一時間閱讀, 本文 GitHub org_hejianhui/JavaStudy 已經收錄,歡迎 Star。