從PO, DTO到Domain Driven Design


從PO, DTO到Domain Driven Design

前言

隨着各種模式的層出不窮(MVC, MVP, MVVM...), 一批新概念一批跟一批接上來. 日常開發中經常會使用到PO, DO, BO, VO, DTO. 有時候可能用了很久 也沒弄清楚到底怎么區分. 下面我們簡單梳理一下

PO, DO, BO, AO, VO, DTO, POJO是什么?

  • DTO(Data Transfer Object)數據傳輸對象
  • VO(Value Object)值對象
  • AO(Application Object)應用對象
  • BO(Business Object)業務對象
  • PO(Persistent Object)持久對象
  • DO (Data Object) = PO & (Domain Object) = BO
  • POJO(plain ordinary java object) 簡單無規則 java 對象

情況下面示意圖

332TzR.png

    基本上看下來就能清楚大概了.

DTO(Data Transfer Object) 數據傳輸對象

概念源於J2EE的設計模式,原來的目的是為了EJB的分布式應用提供粗粒度的數據實體, 減少分布式調用的次數, 提高性能,降低網絡復雜.

但是現在用於服務與服務之間的數據傳輸對象.

VO (View Object) 視圖對象

用於展示層,它的作用是把某個指定頁面(或組件)的所有數據封裝起來.

通常是Web向模板渲染引擎層傳輸的對象.

AO(Application Object)應用對象

在 Web 層與 Service 層之間抽象的復用對象模型,極為貼 近展示層,復用度不高.

BO (Business Object) 業務對象

業務層中使用 ,是 業務對象,封裝對象、復雜對象, 可能包含多個類.

由Service層輸出的封裝業務邏輯的對象.

PO (Persistent Object) 持久對象

此對象與數據庫表結構一一對應,通過 DAO 層向上傳輸數據源對象.

DO 是什么?

  • 阿里巴巴的開發手冊

    DO( Data Object)這個等同於上面的PO

  • DDD[(Domain-Driven Design)領域驅動設計](#Domain Driven Design 領域驅動設計)

    DO(Domain Object)這個等同於上面的BO

POJO(plain ordinary java object) 簡單無規則 java 對象

純的傳統意義的 java 對象.重在簡單.

有一些private的參數作為對象的屬性,針對每一個參數定義get和set方法訪問的接口.
沒有從任何類繼承、也沒有實現任何接口,更沒有被其它框架侵入的java對象.

Domain Driven Design 領域驅動設計

上文中 DO 提到 DDD (Domain Driven Design)領域驅動設計

架構分成了Interfaces、Applications和Domain三層以及包含各類基礎設施的Infrastructure.

架構概述

官方的sample工程,名為DDDSample.

該工程給出了一種實踐領域驅動設計的參考架構,本文將對此該架構進行簡單介紹,並就一些重要問題進行討論.

架構分成了Interfaces、Applications和Domain三層關系如下:

332OeK.gif

詳細架構圖:

332qL6.gif

作為參照,下圖展示了傳統TransactionScript風格的架構:

332bsx.gif

Transaction Script風格的架構具有明顯的“數據”與“操作”分離的特征,其和領域驅動設計風格的架構在兩個類組件上有質的區別,一個是領域對象,一個是Service.

領域驅動設計的架構核心目標是要創建一個富領域模型,其典型特征是它的領域對象具有豐富的業務方法用以處理業務邏輯,而Transaction Script風格的領域對象則僅僅是數據的載體,沒有業務方法.

在Service方面,領域驅動設計的架構里Service是非常“薄“的一層,其並不負責處理業務邏輯,而在Transaction Script風格的架構里,Service是處理業務邏輯的主要場所,因而往往非常厚重.

架構詳解

一. Interfaces 接口層

主要包含與其他系統進行交互的接口與通信設施,在多數應用里,該層可能提供包括Web Services、RMI或Rest等在內的一種或多種通信接口.該層主要由Façade、DTO和Assembler三類組件構成,三類組件均是典型的J2EE模式.

1. DTO

[DTO(Data Transfer Object) 數據傳輸對象](DTO(Data Transfer Object) 數據傳輸對象)

332oW9.gif

2. Assembler

在引入DTO后,DTO與領域對象之間的相互轉換工作多由Assembler承擔

也有用反射機制自動實現DTO與領域對象之間的相互轉換,Apache的Commons BeanUtils就提供了類似的功能.

使用Assembler進行對象數據交換更為安全與可控,並且接受編譯期檢查,但是代碼量明顯偏多.使用反射機制自動進行象數據交換雖然代碼量很少,但卻是非常脆弱的,一旦對象屬性名發生了變化,數據交互就會失敗,並且很難追蹤發現.

3325i4.gif

3. Facade

作為一種設計模式同時也是Interfaces層內的一類組件,Facade的用意在於為遠程客戶端提供粗粒度的調用接口.

Facade本身不處理任何的業務邏輯,它的主要工作就是將一個用戶請求委派給一個或多個Service進行處理,同時借助Assembler將Service傳入或傳出的領域對象轉化為DTO進行傳輸.

332XdO.gif

二. Application應用層

Application層中主要組件是Service,在領域驅動設計的架構里,Service的組織粒度和接口設計依據與傳統Transaction Script風格的Service是一致的,但是兩者的實現卻有着質的區別.

Transaction Script風格的Service是實現業務邏輯的主要場所,因此往往非常厚重.而在領域驅動設計的架構里,Application是非常“薄”的一層,所有的Service只負責協調並委派業務邏輯給領域對象進行處理,其本身並真正實現業務邏輯,絕大部分的業務邏輯都由領域對象承載和實現了,這是區別系統是Transaction Script架構還是Domain Model架構的重要標志.

3322LV.gif

Service的接口是面向用例設計的,是控制事務、安全的適宜場所.如果Facade的某一方法需要調用兩個以上的Service方法,需要注意事務問題.

Domain領域層

Domain層是整個系統的核心層,該層維護一個使用面向對象技術實現的領域模型,幾乎全部的業務邏輯會在該層實現.Domain層包含Entity(實體)、Value Object(值對象)、Domain Event(領域事件)和Repository(倉儲)等多種重要的領域組件.

三. Infrastructure 基礎設施層

作為基礎設施層,Infrastructure為Interfaces、Application和Domain三層提供支撐.所有與具體平台、框架相關的實現會在Infrastructure中提供,避免三層特別是Domain層摻雜進這些實現,從而“污染”領域模型.Infrastructure中最常見的一類設施是對象持久化的具體實現.

看到這里可能還雲里霧里, 下面我們看一張圖

30zo8I.gif

從這張圖我們可以看出, 打薄了service, 強調了domain, 把邏輯都放入了domain中實現.

聚合根(Aggreate Root)

聚合根(Aggreate Root, AR)就是軟件模型中那些最重要的以名詞形式存在的領域對象,比如本文示例項目中的order和product.

然而,並不是說領域模型中的所有名詞都可以建模為聚合根.所謂“聚合”,顧名思義,即需要將領域中高度內聚的概念放到一起組成一個整體.

對內聚性的追求會自然地延伸出聚合根的邊界.在DDD的戰略設計中,我們已經通過限界上下文的划分將一個大的軟件系統拆分為了不同的“模塊”,在這樣的前提下,再在某個限界上下文中來討論內聚性將比在大泥球系統中討論變得簡單得多.

對聚合根的設計需要提防上帝對象(God Object),也即用一個大而全的領域對象來實現所有的業務功能.上帝對象的背后存在着一種表面上看似合理的邏輯:既然要內聚,那么讓我們把所有相關的東西都聚到一起吧,比如用一個Product類來應付所有的業務場景,包括訂單、物流、發票等等.這種機械的方式看似內聚,實則恰恰是內聚性的反面.

  • 聚合根的實現應該與框架無關
  • 聚合根之間的引用通過ID完成
  • 聚合根內部的所有變更都必須通過聚合根完成
  • 如果一個事務需要更新多個聚合根,首先思考一下自己的聚合根邊界處理是否出了問題,因為在設計合理的情況下通常不會出現一個事務更新多個聚合根的場景.如果這種情況的確是業務所需,那么考慮引入消息機制事件驅動架構,保證一個事務只更新一個聚合根,然后通過消息機制異步更新其他聚合根.
  • 聚合根不應該引用基礎設施.
  • 外界不應該持有聚合根內部的數據結構.
  • 盡量使用小聚合.

332gs0.jpg

資源庫(Repository)

用來持久化聚合根的.從技術上講,Repository和DAO所扮演的角色相似,不過DAO的設計初衷只是對數據庫的一層很薄的封裝,而Repository是更偏向於領域模型.

應用服務作為總體協調者,先通過資源庫獲取到聚合根,然后調用聚合根中的業務方法,最后再次調用資源庫保存聚合根.

Command 對象

DDD中的寫操作並不需要向客戶端返回數據,在某些情況下(比如新建聚合根)可以返回一個聚合根的ID,這意味着ApplicationService或者聚合根中的寫操作方法通常返回void即可.

從技術上講,Command對象只是一種類型的DTO對象,它封裝了客戶端發過來的請求數據.在Controller中所接收的所有寫操作都需要通過Command進行包裝

DDD中的讀操作

領域模型的讀操作

這種方式將讀模型和寫模型糅合到一起,先通過資源庫獲取到領域模型,然后將其轉換為我們需要的VO.

這樣導致的結果是Repository上處理了太多的查詢邏輯,變得越來越復雜,也逐漸偏離了Repository本應該承擔的職責.不推薦使用.

數據模型的讀操作

這種方式繞開了資源庫和聚合,直接從數據庫中讀取客戶端所需要的數據,此時寫操作和讀操作共享的只是數據庫.

微軟也提倡過這種方式,更多細節請參考微軟官網.

CQRS (Command Query Responsibility Segregation)

即命令查詢職責分離,這里的命令可以理解為寫操作,而查詢可以理解為讀操作.即是邏輯上的讀寫分離.

傳統做法是通過DB 識別sql 的讀寫操作, 寫操作通過主數據庫處理事務的增刪改,讓從數據庫處理查詢操作(Select操作),數據庫復制被用來將事務性操作導致的變更同步到集群中的從數據庫.

332IJJ.jpg

總結

  1. 首先我們了解了 [PO, DO, BO, AO, VO, DTO, POJO](#PO, DO, BO, AO, VO, DTO, POJO是什么?)
  2. 討論DDD設計模式對於讀操作,同樣給出了3種方式:
    • 基於領域模型的讀操作(讀寫操作糅合在了一起,不推薦)
  • 基於數據模型的讀操作(繞過聚合根和資源庫,直接返回數據,推薦)
    • CQRS(讀寫操作分別使用不同的數據庫)

歡迎評論 交流 點贊

30vtGq.gif


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM