領域驅動設計(Domain Driven Design)參考架構詳解
1. 架構概述
領域驅動設計(Domain Driven Design)有一個官方的sample工程,名為DDDSample,官網:http://dddsample.sourceforge.net/,該工程給出了一種實踐領域驅動設計的參考架構,本文將對此該架構進行簡單介紹,并就一些重要問題進行討論。
該架構分成了Interfaces、Applications和Domain三層以及包含各類基礎設施的Infrastructure。下圖簡略描述了它們之間的關系:
圖1 領域驅動設計風格的架構草圖(來自于DDDSample官網)
下圖是詳細架構:
圖2 領域驅動設計參考架構
作為參照,下圖展示了傳統TransactionScript風格的架構,可以看出,兩者的差異并不是太大(對于Fa?ade來說,它是一種可選設施,如果系統架構中省略Fa?ade,則DTO與領域對象的互換工作可在service中進行),這也從則面說明推行領域驅動設計的關鍵并不在架構上,而在于整個團隊在分析、設計和開發上沒有自始至終地以領域模型為核心開展工作,以面向對象的思想進行設計和編程。
Transaction Script風格的架構具有明顯的“數據”與“操作”分離的特征,其和領域驅動設計風格的架構在兩個類組件上有質的區別,一個是領域對象,一個是Service。領域驅動設計的架構核心目標是要創建一個富領域模型,其典型特征是它的領域對象具有豐富的業務方法用以處理業務邏輯,而Transaction Script風格的領域對象則僅僅是數據的載體,沒有業務方法,這種領域也被稱作“貧血的領域對象”(Anemic Domain Objects)。在Service方面,領域驅動設計的架構里Service是非常“薄“的一層,其并不負責處理業務邏輯,而在TransactionScript風格的架構里,Service是處理業務邏輯的主要場所,因而往往非常厚重。
圖3. 數據與操作分離的Transaction Script風格的架構
2. 架構詳解
2. 1 Interfaces-接口層
領域驅動設計對Interfaces的定位是:
Thislayer holds everything that interacts with other systems, such as web services,RMI interfaces or web applications, and batch processing frontends. It handlesinterpretation, validation and translation of incoming data. It also handlesserialization of outgoing data, such as HTML or XML across HTTP to web browsersor web service clients, or DTO classes and distributed facade interfaces forremote Java clients.
該層包含與其他系統進行交互的接口與通信設施,在多數應用里,該層可能提供包括Web Services、RMI或Rest等在內的一種或多種通信接口。該層主要由Facade、DTO和Assembler三類組件構成,三類組件均是典型的J2EE模式,以下是對三類組件的具體介紹:
2.1.1 DTO
DTO- DataTransfer Object(數據傳輸對象),也常被稱作VO-Value Object(值對象)。基于面向對象技術設計的領域對象(即通常所說的“實體”)都是細粒度的,將細粒度的領域對象直接傳遞到遠程調用端需要進行多次網絡通信,DTO在設計之初的主要考量是以粗粒度的數據結構減少網絡通信并簡化調用接口。以下羅列了DTO的多項作用:
- Reduces network traffic
- Simplifies remote object and remote interface
- Transfers more data in fewer remote calls
- Reduces code duplication
- Introduces stale transfer objects
- Increases complexity due to synchronization and version control
圖4. DTO應用時序圖(基于《Core J2EE Patterns》插圖進行了修改)
值得一提的是,DTO對實現一個獨立封閉的領域模型具有積極的作用,特別是當系統使用了某些具有自動臟數據檢查(automatic dirty checking)機制的ORM框架時,DTO的優勢就更加明顯,否則就會存在領域對象在模型層以外被意外修改并自動持久化到數據庫中的風險或者是像Hibernate那樣的框架因未開啟OpenSessionInView (注:開啟OpenSessionInView有副作用,一般認為OpenSessionInView不是一種好的實踐)而導致Lazy Loading出現問題。
關于DTO具體的設計用意和應用場景可參考如下資源:
- 《Core J2EE? Patterns: Best Practices and Design Strategies, SecondEdition》
- 《Patterns of Enterprise ApplicationArchitecture》
2.1.2 Assembler
在引入DTO后,DTO與領域對象之間的相互轉換工作多由Assembler承擔,因此Assembler幾乎總是同DTO一起出現。也有一些系統使用反射機制自動實現DTO與領域對象之間的相互轉換,Appache的Commons BeanUtils就提供了類似的功能。應該說這兩種實現各有利弊,使用Assembler進行對象數據交換更為安全與可控,并且接受編譯期檢查,但是代碼量明顯偏多。使用反射機制自動進行象數據交換雖然代碼量很少,但卻是非常脆弱的,一旦對象屬性名發生了變化,數據交互就會失敗,并且很難追蹤發現。總體來說,Assembler更為直白和穩妥。
圖5. Assebler應用類圖(基于《Core J2EE Patterns》插圖進行了修改)
關于Assembler具體的設計用意和應用場景可參考如下資源:
- 《Core J2EE? Patterns: Best Practices and Design Strategies, SecondEdition》
- 《Patterns of Enterprise ApplicationArchitecture》
2.1.3 Facade
作為一種設計模式同時也是Interfaces層內的一類組件,Facade的用意在于為遠程客戶端提供粗粒度的調用接口。Facade本身不處理任何的業務邏輯,它的主要工作就是將一個用戶請求委派給一個或多個Service進行處理,同時借助Assembler將Service傳入或傳出的領域對象轉化為DTO進行傳輸。以下羅列了Facade的多項作用:
- Introduces a layer that provides services to remote clients
- Exposes a uniform coarse-grained interface
- Reduces coupling between the tiers
- Promotes layering, increases flexibility and maintainability
- Reduces complexity
- Improves performance, reduces fine-grained remote methods
- Centralizes security management
- Centralizes transaction control
- Exposes fewer remote interfaces to clients
實踐Facade的過程中最難把握的問題就是Facade的粒度問題。傳統的Service均以實體為單位進行組織,而Facade應該具有更粗粒度的組織依據,較為合適的粒度依據有:一個高度內聚的模塊一個Facade,或者是一個“聚合”(特指領域驅動設計中的聚合)一個Facade.
圖6. Facade應用類圖(基于《Core J2EE Patterns》插圖進行了修改)
圖7. Facade應用時序圖(基于《Core J2EE Patterns》插圖進行了修改)
關于Assembler具體的設計用意和應用場景可參考如下資源:
- 《Core J2EE? Patterns: Best Practices and Design Strategies, SecondEdition》
- 《Patterns of Enterprise ApplicationArchitecture》
- 《Design Patterns: Elementsof Reusable Object-Oriented Software》
2.2 Application-應用層
領域驅動設計對Application的定位是:
Theapplication layer is responsible for driving the workflow of the application,matching the use cases at hand. These operations are interface-independent andcan be both synchronous or message-driven. This layer is well suited forspanning transactions, high-level logging and security. The application layeris thin in terms of domain logic - it merely coordinates the domain layerobjects to perform the actual work.
Application層中主要組件就是Service,在領域驅動設計的架構里,Service的組織粒度和接口設計依據與傳統Transaction Script風格的Service是一致的,但是兩者的實現卻有著質的區別。TransactionScript風格的Service是實現業務邏輯的主要場所,因此往往非常厚重。而在領域驅動設計的架構里,Application是非常“薄”的一層,所有的Service只負責協調并委派業務邏輯給領域對象進行處理,其本身并真正實現業務邏輯,絕大部分的業務邏輯都由領域對象承載和實現了,這是區別系統是Transaction Script架構還是Domain Model架構的重要標志。
不管是Transaction Script風格還Domain Model風格,Service都會與多種組件進行交互,這些組件包括:其他的Service、領域對象和Repository 或 DAO。
圖8. Service應用時序圖(基于《Core J2EE Patterns》插圖進行了修改)
Service的接口是面向用例設計的,是控制事務、安全的適宜場所。如果Facade的某一方法需要調用兩個以上的Service方法,需要注意事務問題。
2.3 Domain-領域層
領域驅動設計對Domain的定位是:
Thedomain layer is the heart of the software, and this is where the interestingstuff happens. There is one package per aggregate, and to each aggregatebelongs entities, value objects, domain events, a repository interface andsometimes factories.
Thecore of the business logic belongs in here. The structure and naming ofaggregates, classes and methods in the domain layer should follow theubiquitous language, and you should be able to explain to a domain expert howthis part of the software works by drawing a few simple diagrams and using theactual class and method names of the source code.
Domain層是整個系統的核心層,該層維護一個使用面向對象技術實現的領域模型,幾乎全部的業務邏輯會在該層實現。Domain層包含Entity(實體)、ValueObject(值對象)、Domain Event(領域事件)和Repository(倉儲)等多種重要的領域組件。
2.4 Infrastructure-基礎設施層
領域驅動設計對Infrastructure的定位是:
Inaddition to the three vertical layers, there is also the infrastructure. As thethe picture shows, it supports all of the three layers in different ways,facilitating communication between the layers. In simple terms, theinfrastructure consists of everything that exists independently of ourapplication: external libraries, database engine, application server, messagingbackend and so on.
Also,we consider code and configuration files that glues the other layers to theinfrastructure as part of the infrastructure layer. Looking for example at thepersistence aspect, the database schema definition, Hibernate configuration andmapping files and implementations of the repository interfaces are part of theinfrastructure layer.
Whileit can be tricky to give a solid definition of what kind of code belongs to theinfrastructure layer for any given situation, it should be possible tocompletely stub out the infrastructure in pure Java unit/scenario tests andstill be able to use the domain layer and possibly the application layer towork out the core business problems.
作為基礎設施層,Infrastructure為Interfaces、Application和Domain三層提供支撐。所有與具體平臺、框架相關的實現會在Infrastructure中提供,避免三層特別是Domain層摻雜進這些實現,從而“污染”領域模型。Infrastructure中最常見的一類設施是對象持久化的具體實現。
3. 關于架構的一些討論
3.1 架構并不能保證領域驅動設計的貫徹與執行
雖然一個合適的架構對于實施領域驅動設計是大有必要的,但只依靠架構是不能保證領域驅動設計的貫徹與執行的。實際上,在這個參考架構上使用Transaction Script的方式進行開法幾乎沒有任何問題,只要開發人員將領域對象變成“貧血”的“數據載體”對待,在service里實現業務邏輯,那么該參考架構將成為純粹的TransactionScript方式。當然反過來看,這也體現了這一架構的靈活性。確保領域驅動設計的貫徹與執行需要整個團隊在分析、設計和開發上沒有自始至終地以領域模型為核心開展工作,以面向對象的思想進行設計和編程,才能保證實現領域驅動設計。
3.2 Facade是否是必須的?
盡管在架構中對Facade的定義非常清晰,但在實踐中我發現Facade并不是一個容易拿捏的東西。主要問題在于其與Service之間的有太多的重疊與相似之處。我們注意到Service是接口是面向一個use case的,因此事務也是追加在Service這一層上,于是對于Facade而言,99%的情況是,它只是把某個Service的某個方法再包裹一下而已,如果把領域對象和DTO的互轉換工作移至Service中進行,那么Facade將徹底變成空殼,而關鍵的是:如果Service的接口設計是面向和user case的,那么,毫無疑問,Service接口的傳入傳出參數也都應該是DTO,而這一點也在《Core J2EE Patterns: Best Practices and Design Strategies, SecondEdition》和《Patterns of Enterprise ApplicationArchitecture》兩書的示例代碼中完全印證了。那么,從更為務實角度出發,Facade并非是一種必須的組件。