|
領域驅動設計(DDD)的概念源于2004年著名建模專家Eric Evans發表的書籍:《Domain-Driven Design – Tackling Complexity in the Heart of Software》(中文譯名:領域驅動設計—軟件核心復雜性應對之道),池建強在2011年發表的一篇文章《領域驅動設計和實踐》中是這樣形容DDD的:
領域驅動設計事實上是針對OOAD的一個擴展和延伸,DDD基于面向對象分析與設計技術,對技術架構進行了分層規劃,同時對每個類進行了策略和類型的劃分。
本文主要介紹為什么我們在恒拓開源內部推廣DDD,我們如何通過開發 DDDLib 和 Koala 等工具來完善這一過程,推廣過程中遇到了哪些問題,以及我們如何解決這一問題。
為什么選擇DDD
傳統的模式的最大優點在于開發人員非常熟悉,開發成本低,但它也有一些問題:
采用DDD開發模式之前,傳統的開發模型是最流行的Model-Dao-Service-UI開發模型,通常是基于事務腳本(Transaction Script)和表模塊(Table Module)模式的實現,這種模式通常是先設計表,再建模,實現容易依賴特定的表的一些特性,如存儲過程。基于表的設計模式容易帶來以下幾個問題:
- 業務建模完全是表的復制,無法真實反映業務。
- 核心業務分散在各個地方,非常危險,修改擴展難,且難以閱讀。
這種開發模式適合一些需求小,后續維護擴展需求小的中小型項目,但在大型企業級系統或產品,擴展維護或需求變量非常多的情況下,缺點也非常明顯。
相對而言,DDD則有以下四點好處:
1、面向對象,模型真實反映業務現實:使用DDD領域驅動設計,模型通常是業務的真實反映,業務集中在領域而不是分散在各Service中,有利于對業務的理解。
2、使用領域統一建模語言:有利于業務溝通與建模: DDD倡導先對業務建模,而非關注表或腳本的設計;在建模過程中,由于領域本身是對真實業務的反映與建模,因此與業務專家更容易溝通,打破技術與業務的溝通隔閡。
3、可重用性高:DDD中,領域層為核心,每個領域對象都是一個相對完整的內聚的業務對象描述,所以可以形成直接的復用。基于領域建模的設計,并不會依賴特定的數據庫及特性,模型是可以完全重用且沒有技術上的沖突。
4、業務越復雜,DDD的優勢越明顯:領域模型采用OO設計,通過將職責分配到相應的模型對象或Service,可以很好的組織業務邏輯,當業務變得復雜時,領域模型顯出巨大的優勢。
DDDLib登場
DDD本質上是一種思想,并不是新技術。在恒拓開源,由我們的楊宇老師和陳操總共同創作的DDDLib庫,是對DDD思想的核心支持與實現。
DDDLib是一整套支持DDD思想實現的類庫,DDDLib下還是使用的 Hibernate、JPA或MyBatis、noSQL等技術為實現。
如同DDD所要求樣,使用DDDLib 的項目分層圖為:
- 用戶界面/展現層
- 用于向用戶展現信息,處理用戶在界面上的請求,比如struts,tapestry,springMVC等頁面框架。
- 應用層
- 用來處理應用的活動,不包含領域中的業務邏輯。
- 應用層可以用來處理一些與領域概念無關的攔截性質的工作,比如日志,事務等。此外,應用層也可以用來處理一些既不屬于展現層,也不屬于領域層,而是屬于目前應用相關的一些服務。比如資金轉賬的業務的讀取輸入功能(讀取輸入不是轉賬的核心業務含義)。
- 領域層
- 此層是DDD的核心:領域對象,領域服務,倉儲接口均位于此層。
- 領域的信息,是業務軟件的核心所在。
- 需要保留業務對象的狀態,對業務對象及其狀態持久化的操作交給基礎設施層。
- 領域層應該遵從以下原則:除非業務發生變化,否則其他任何變化均不應該影響到領域層。這些其他變化包括:不同的展現框架,不同的頁面展現內容,是否要分頁,是否支持手機客戶端,是否公開WebService,是否提供OpenAPI等等。
- 基礎設施層
DDDLib的核心實現如下:
上圖就是使用 DDDLib 項目的整體技術架構圖,也表明了DDDLib的整體原則:
- 領域層是業務核心,這一層不依賴任何特定的技術框架,保證它的業務純潔性。DDDLib中的領域層只依賴JDK、DDDLib的Domain庫以及倉儲接口及其它自定義接口。
- 使用倉儲和查詢通道作為與存儲介質相關的操作接口,隔離對特定數據庫技術或存儲介質的依賴。
- 提供多種不同的IoC容器實現及InstanceFactory實例工廠,隔離對特定IOC 技術的依賴。
- 領域層包括值對象、領域對象以及領域服務三個要素,領域層不是數據庫操作層而是業務建模層。許多開發者在使用的過程中,最終還是把領域層作為數據庫操作層來使用,對實體的方法也是以數據庫的操作行為為標準 ,如查詢,新增,刪除一個實體,最終依然回歸到以數據庫為中心的方向去了,這是需要避免的。
- 改變以數據庫為中心的核心是意識到業務行為才是核心,數據庫存儲是支撐。意識到數據庫是支撐非常關鍵,業務上的任何行為,在系統中最終需要存儲記錄,數據庫存儲是對業務實現的支撐,也可以使用文件,緩存或云空間等其它存儲介質。想像一下,使用數據庫進行設計的項目,最終就限定了存儲介質為特定的數據庫,如果下一次需要更換為云空間或緩存等其它存儲形式,就會發現整個系統需要重新設計開發,但使用DDDLib,只需更換倉儲實現,提供一個云空間的實現就行了,核心業務邏輯完全不需要變動與修改。
DDDLib在實現過程中也經歷了內部的不少爭議,經過很多次的討論和打磨形成了現在的格局。在下面的部分,我將介紹DDDLib在幾個重要組件上的實現細節。
DDDLib倉儲的實現
從DDDLib 1.0到3.5版本,倉儲實現歷經幾個階段,分別是:
- 給每個領域對象定義一個倉儲接口及一個倉儲實現。
這種倉儲實現非常受爭議,開發人員并不認可這種方式,倉儲接口及實現非常多,一方面導致項目類太多,并且也帶來編碼的重復操作。
- 與spring data整合,給每個領域對象定義倉儲接口,無須定義實現。這種模式對前面的模式有了優化,只定義接口不定義實現,但是spring data這種依賴方法名,參數來進行查詢的模式,針對一些復雜的查詢,難以勝任。
- 提供默認的hibernate及JPA的通用倉儲接口。
為每個倉儲定義一個接口,這種模式慢慢的不被接受,使用spring data帶來的優化方案,也有非常多的問題,后面根據JPA的實體管理思路,于是形成了通用倉儲接口及不同技術實現的思路,定義一個通用的倉儲接口,包括通用的增刪改查數據庫行為。
- 支持MyBatis的通用倉儲接口
DDDLib的JPA及hibernate倉儲實現,這個方案是一個較佳的方案,所有領域實體使用通用的倉儲,避免了大量重復代碼,但DDDLib一直是基于Hibernate/JPA提供的實現與技術支持,在項目的使用過程中,經常會遇到不適合使用Hibernate/JPA模式的項目,對MyBatis的需求也非常大,這種情況下,Koala團隊定義實現了MyBatis的倉儲實現,并保證其與JPA/Hibernate模式下API的一致性。
DDDLib中的DTO
DTO,數據傳輸對象,領域對象雖然有數據(屬性),但是領域對象上面還帶有操作,在某些場合不適合進行傳輸,有些時候傳輸還需要序列化。但并不是所有的領域對象屬性都可以暴露,而且有些屬性可能要合并,可能要分解,之后才有利于前端的使用。于是就有了專門用來傳輸數據的DTO,只有屬性,沒有操作,必要的時候加上序列化標記,實現遠程調用。
這是DDD中DTO的作用,但是DTO同時也帶來了實體與DTO的轉換性能問題,在大數據量下尤其明顯。
DDDLib中的數據庫支撐行為類
在DDDLib實現中,提供了Repository以及QueryChannelService兩個接口,分別使用在領域層以及應用層,都是對數據庫的操作接口。
SQL/HQL/JPQL寫在哪
在使用DDDLib的過程中,不同的持久層框架的SQL語言不一樣,比如MyBatis使用的是SQL,Hibernate使用的是 HQL,JPA下使用的是JPQL。
這些語句寫在哪兒在公司也經歷過一番爭議與變更,歷史如下:
1、寫在代碼中
public static Resource newResource(String name,String identifier,String level,String menuIcon){ Resource resource = null; List<Resource> resources = Resource.getRepository().find("select r from Resource r where r.name = ? " + "and r.identifier = ?", new Object[]{name,identifier}, Resource.class); ...}
it知識庫:DDD &amp; DDDLib在恒拓開源的發展歷程與推廣經驗,轉載需保留來源!
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。