領域驅動設計(簡稱 DDD)概念來源於2004年著名建模專家Eric Evans發表的他最具影響力的書籍:《Domain-Driven Design –Tackling Complexity in the Heart of Software》(中文譯名:領域驅動設計—軟件核心複雜性應對之道)一書。,書中提出了「領域驅動設計(簡稱 DDD)」的概念。程序員
領域驅動設計通常分爲兩個階段:數據庫
1. 以一種領域專家、設計人員、開發人員都能理解的「通用語言」做爲相互交流的工具,在不斷交流的過程當中發現和挖出一些主要的領域概念,而後將這些概念設計成一個領域模型;編程
2. 由領域模型驅動軟件設計,用代碼來表現該領域模型。領域需求的最初細節,在功能層面經過領域專家的討論得出。緩存
領域驅動設計告訴咱們,在經過軟件實現一個業務系統時,創建一個領域模型是很是重要和必要的,由於領域模型具備如下特色:安全
1. 領域模型是對具備某個邊界的領域的一個抽象,反映了領域內用戶業務需求的本質;領域模型是有邊界的,只反應了咱們在領域內所關注的部分;網絡
2. 領域模型只反映業務,和任何技術實現無關;領域模型不只能反映領域中的一些實體概念,如貨物,書本,應聘記錄,地址,等;還能反映領域中的一些過程概念,如資金轉帳,等;數據結構
3. 領域模型確保了咱們的軟件的業務邏輯都在一個模型中,都在一個地方;這樣對提升軟件的可維護性,業務可理解性以及可重用性方面都有很好的幫助;架構
4. 領域模型可以幫助開發人員相對平滑地將領域知識轉化爲軟件構造;app
5. 領域模型貫穿軟件分析、設計,以及開發的整個過程;領域專家、設計人員、開發人員經過領域模型進行交流,彼此共享知識與信息;由於你們面向的都是同一個模型,因此能夠防止需求走樣,可讓軟件設計開發人員作出來的軟件真正知足需求;框架
6. 要創建正確的領域模型並不簡單,須要領域專家、設計、開發人員積極溝通共同努力,而後才能使你們對領域的認識不斷深刻,從而不斷細化和完善領域模型;
7. 爲了讓領域模型看的見,咱們須要用一些方法來表示它;圖是表達領域模型最經常使用的方式,但不是惟一的表達方式,代碼或文字描述也能表達領域模型;
8. 領域模型是整個軟件的核心,是軟件中最有價值和最具競爭力的部分;設計足夠精良且符合業務需求的領域模型可以更快速的響應需求變化;
領域驅動設計中的一些基本概念:
根據Eric Evans的定義,」一個由它的標識定義的對象叫作實體」。一般實體具有惟一ID,可以被持久化,具備業務邏輯,對應現實世界業務對象。
實體通常和主要的業務/領域對象有一個直接的關係。一個實體的基本概念是一個持續抽象的生命,能夠變化不一樣的狀態和情形,但老是有相同的標識。
須要注意的是:
一些開發人員將實體當成了ORM意義上的實體,而不是業務全部和業務定義的領域對象。在一些實現中採用了Transaction Script風格的架構,使用貧血的領域模型。這種認識上的混亂,在領域驅動架構中,不肯意在領域對象中加入業務邏輯而致使貧血的領域模型,同時還可能使混亂的服務對象激增。
值對象的定義是:描述事物的對象;更準確的說,一個沒有概念上標識符描述一個領域方面的對象。這些對象是用來表示臨時的事物,或者能夠認爲值對象是實體的屬性,這些屬性沒有特性標識但同時表達了領域中某類含義的概念。
一般值對象不具備惟一ID,由對象的屬性描述,能夠用來傳遞參數或對實體進行補充描述。
做爲實體屬性的描述時,值對象也會被存儲。在UML的類圖上顯現爲一對多或一對一的關係。在ORM映射關係上須要採用較複雜的一對多或一對一關係映射。
關於實體與值對象的一個例子:好比員工信息的屬性,如住址,電話號碼均可以改變;然而,同一個員工的實體的標識將保持不變。所以,一個實體的基本概念是一個持續抽象的生命,能夠變化不一樣的狀態和情形,但老是有相同的標識。
實體具備惟一標識,而值對象沒有惟一標識,這是實體和值對象間的最大不一樣。
實體就是領域中須要惟一標識的領域概念。有兩個實體,若是惟一標識不同,那麼即使實體的其餘全部屬性都同樣,也認爲是兩個不一樣的實體;一個實體的基本概念是一個持續抽象的生命,能夠變化不一樣的狀態和情形,但老是有相同的標識。
不該該給實體定義太多的屬性或行爲,而應該尋找關聯,發現其餘一些實體或值對象,將屬性或行爲轉移到其餘關聯的實體或值對象上。
若是兩個對象的全部的屬性的值都相同,咱們會認爲它們是同一個對象的話,那麼咱們就能夠把這種對象設計爲值對象。值對象在判斷是不是同一個對象時是經過它們的全部屬性是否相同,若是相同則認爲是同一個值對象;而實體是否爲同一個實體的區分,只是看實體的惟一標識是否相同,而無論實體的屬性是否相同。
值對象另一個明顯的特徵是不可變,即全部屬性都是隻讀的。由於屬性是隻讀的,因此能夠被安全的共享;當共享值對象時,通常有複製和共享兩種作法,具體採用哪一種作法還要根據實際狀況而定。
箴言:若是值對象時可共享的,它們應該是不可變的。(值對象應該保持儘可能的簡單)
值對象的設計應儘可能簡單,不要讓它引用不少其餘的對象,由於本質上講值對象只是表明一個值。
聚合是用來定義領域對象全部權和邊界的領域模式。聚合的做用是幫助簡化模型對象間的關係。聚合,它經過定義對象之間清晰的所屬關係和邊界來實現領域模型的內聚,並避免了錯綜複雜的難以維護的對象關係網的造成。聚合定義了一組具備內聚關係的相關對象的集合,咱們把聚合看做是一個修改數據的單元。
劃分Aggregation是對領域模型的進一步深化,Aggregation能闡釋領域模型內部對象之間的深層關聯.對Aggregation的劃分會直接映射到程序結構上.好比:DDD推薦按Aggregation設計model的子包.每一個Aggregation配備一個Repository.Aggregation內部的非root對象是經過導航得到的.
一個聚合是一組相關的被視爲總體的對象。每一個聚合都有一個根對象(聚合根實體),從外部訪問只能經過這個對象。根實體對象有組成聚合全部對象的引用,可是外部對象只能引用根對象實體。
只有聚合根才能使用倉儲庫直接查詢,其它的只能經過相關的聚合訪問。若是根實體被刪除,聚合內部的其它對象也將被刪除。
一般,咱們把聚合組織到一個文件夾或一個包中。每個彙集對應一個包,而且每一個彙集成員包括實體、值對象,domain事件,倉儲接口和其它工廠對象。
聚合有如下一些特色:
1. 每一個聚合有一個根和一個邊界,邊界定義了一個聚合內部有哪些實體或值對象,根是聚合內的某個實體;
2. 聚合內部的對象之間能夠相互引用,可是聚合外部若是要訪問聚合內部的對象時,必須經過聚合根開始導航,絕對不能繞過聚合根直接訪問聚合內的對象,也就是說聚合根是外部能夠保持對它的引用的惟一元素;
3. 聚合內除根之外的其餘實體的惟一標識都是本地標識,也就是隻要在聚合內部保持惟一便可,由於它們老是從屬於這個聚合的;
4. 聚合根負責與外部其餘對象打交道並維護本身內部的業務規則;
5. 基於聚合的以上概念,咱們能夠推論出從數據庫查詢時的單元也是以聚合爲一個單元,也就是說咱們不能直接查詢聚合內部的某個非根的對象;
6. 聚合內部的對象能夠保持對其餘聚合根的引用;
7. 刪除一個聚合根時必須同時刪除該聚合內的全部相關對象,由於他們都同屬於一個聚合,是一個完整的概念。
如何識別聚合?
聚合中的對象關係是內聚的,即這些對象之間必須保持一個固定規則,固定規則是指在數據變化時必須保持不變的一致性規則。
當咱們在修改一個聚合時,咱們必須在事務級別確保整個聚合內的全部對象知足這個固定規則。
做爲一條建議,聚合儘可能不要太大,不然即使可以作到在事務級別保持聚合的業務規則完整性,也可能會帶來必定的性能問題。
有分析報告顯示,一般在大部分領域模型中,有70%的聚合一般只有一個實體,即聚合根,該實體內部沒有包含其餘實體,只包含一些值對象;另外30%的聚合中,基本上也只包含兩到三個實體。這意味着大部分的聚合都只是一個實體,該實體同時也是聚合根。
如何識別聚合根?
若是一個聚合只有一個實體,那麼這個實體就是聚合根;若是有多個實體,能夠思考聚合內哪一個對象有獨立存在的意義而且能夠和外部直接進行交互。
並非全部的實體都是彙集根,但只有實體才能成爲彙集根。
工廠用來封裝建立一個複雜對象尤爲是聚合時所需的知識,做用是將建立對象的細節隱藏起來。客戶傳遞給工廠一些簡單的參數,而後工廠能夠在內部建立出一個複雜的領域對象而後返回給客戶。當建立 實體和值對象複雜時建議使用工廠模式。
不意味着咱們必定要使用工廠模式。若是建立對象很簡單,使用構造器或者控制反轉/依賴注入容器足夠建立對象的依賴。此時,咱們就不須要通用工廠模式來建立實體或值對象。
良好工廠的要求:
每一個建立方法都是原子的。一個工廠應該只能生產透明狀態的對象。對於實體,意味着建立整個聚合時知足全部的不變量。
一個單獨的工廠一般生產整個聚合,傳出一個根實體的引用,確保聚合的不變量都有。若是對象的內部聚合須要工廠,一般工廠方法的邏輯放在在聚合根上。這樣對外部隱藏了聚合內聚的實現,同時賦予了根確保聚合完整的職責。若是聚合根不是子實體工廠的合適的家,那麼繼續建立一個單獨的工廠。
倉儲是用來管理實體的集合。
倉儲裏面存放的對象必定是聚合,緣由是Domain是以聚合的概念來劃分邊界的;聚合做爲一個總體概念,要麼一塊兒被取出來,要麼一塊兒被刪除。外部訪問不會單獨對某個聚合內的子對象進行單獨操做。所以,咱們只對聚合設計倉儲。
倉儲還有一個重要的特徵就是分爲倉儲定義部分和倉儲實現部分,咱們在領域模型中定義倉儲的接口,而在基礎設施層實現具體的倉儲。也符合按照接口分離模式在領域層定義倉儲庫接口的原則。
注意:Repositories自己是一種領域組件,但Repositories的實現卻不是領域層中的。
DAO和Repository在領域驅動設計中都很重要。DAO是面向數據訪問的,是關係型數據庫和應用之間的契約。
Repository:位於領域層,面向Aggregation Root。Repository是一個獨立的抽象,使用領域的通用語言,它與DAO進行交互,並使用領域理解的語言提供對領域模型的數據訪問服務的「業務接口」。
DAO方法是細粒度的,更接近數據庫,而Repository方法的粒度粗一些,並且更接近領域。領域對象應該只依賴於Repository接口。客戶端應該始終調用領域對象,領域對象再調用DAO將數據持久化到數據 存儲中。
處理領域對象之間的依賴關係(好比實體及其Repository之間的依賴關係)是開發人員常常遇到的典型問題。解決這個問題通 常的設計方案是讓服務類或外觀類直接調用Repository,在調用Repository的時候返回實體對象給客戶端。
服務這個詞在服務模式中是這麼定義的:服務提供的操做是它提供給使用它的客戶端,並突出領域對象的關係。
全部的Service只負責協調並委派業務邏輯給領域對象進行處理,其自己並真正實現業務邏輯,絕大部分的業務邏輯都由領域對象承載和實現了。
Service可與多種組件進行交互,這些組件包括:其餘的Service、領域對象和Repository 或 DAO。
一般,應用中通常包括:Domain模型服務和應用層服務:
* Domain services encapsulate domain concepts that just are not naturally modeled as things.
* Application services constitute the application, or service, layer.
當一個領域操做被視爲一個重要的領域概念,通常就應該做爲領域服務。 服務應該是無狀態的。
設計實現領域服務來協調業務邏輯,只在領域服務中實現領域邏輯的調用。
領域服務邏輯須以很是乾淨簡潔的代碼實現。所以,咱們必須實現對領域低層組件的調用。一般應用的調用,例如倉儲庫的調用,建立事務等,不該該在這裏實現。這些操做應該在應用層實現。
一般服務對象名稱中都應包含一個動詞。 Service接口的傳入傳出參數也都應該是DTO,可能包含的工做有領域對象和DTO的互轉換以及事務。
服務的3個特徵:
a. 服務執行的操做涉及一個領域概念,這個領域概念一般不屬於一個實體或者值對象
b. 被執行的操做涉及到領域中其它的對象
c. 操做時無狀態的
推薦:最好顯式聲明服務,由於它建立了領域中一個清晰的特性,封裝了一個概念領域層服務和基礎設施層服務:均創建在領域實體和值對象的上層,以便直接爲這些相關的對象提供所需的服務;
通常的領域對象都是有狀態和行爲的,而領域服務沒有狀態只有行爲。須要強調的是領域服務是無狀態的,它存在的意義就是協調領域對象共同完成某個操做,全部的狀態仍是都保存在相應的領域對象中。
一般,對開發人員來講建立不該該存在的服務至關容易;要麼在服務中包含了本應存在於領域對象中的領域邏輯,要麼扮演了缺失的領域對象角色,而這些領域對象並無做爲模型的一部分去建立。
Domain Event模式最初由udi dahan提出,發表在本身的博客上:http://www.udidahan.com/2009/06/14/domain-events-salvation/
企業級應用程序事件大體能夠分爲三類:系統事件、應用事件和領域事件。領域事件的觸發點在領域模型(Domain Model)中。它的做用是將領域對象從對repository或service的依賴中解脫出來,避免讓領域對象對這些設施產生直接依賴。它的作法就是當領域對象的業務方法須要依賴到這些對象時就發出一個事件,這個事件會被相應的對象監聽到並作出處理。
經過使用領域事件,咱們能夠實現領域模型對象狀態的異步更新、外部系統接口的委託調用,以及經過事件派發機制實現系統集成。另外,領域事件自己具備自描述性。它不只可以表述系統發生了什麼事情,並且還可以描述發生事件的動機。
Domain事件也用表進行存儲。
DTO- DataTransfer Object(數據傳輸對象):DTO在設計之初的主要考量是以粗粒度的數據結構減小網絡通訊並簡化調用接口。
Eric Evans的「領域驅動設計- 應對軟件的複雜性「一書中描述和解釋了建議的N層架構高層次的圖:
User Interface:
該層包含與其餘系統/客戶進行交互的接口與通訊設施,在多數應用裏,該層可能提供包括Web Services、RMI或Rest等在內的一種或多種通訊接口。該層主要由Facade、DTO和Assembler三類組件構成,三類組件均是典型的J2EE模式。
DTO的做用最初主要是以粗粒度的數據結構減小網絡通訊並簡化調用接口。在領域驅動設計中,採用DTO模型,能夠起到隱藏領域細節,幫助實現獨立封閉的領域模型的做用。
DTO與領域對象之間的相互轉換工做多由Assembler承擔,也有一些系統使用反射機制自動實現DTO與領域對象之間的相互轉換,如Apache Common BeanUtils。
Facade的用意在於爲遠程客戶端提供粗粒度的調用接口。Facade自己不處理任何的業務邏輯,它的主要工做就是將一個用戶請求委派給一個或多個Service進行處理,同時藉助Assembler將Service傳入或傳出的領域對象轉化爲DTO進行傳輸。
Application:
Application層中主要組件就是Service。這裏須要注意的是,Service的組織粒度和接口設計依據與傳統Transaction Script風格的Service是一致的,可是二者的實現卻有質的區別。
Transaction Script(事務腳本)的核心是過程,經過過程的調用來組織業務邏輯,業務邏輯在服務(Service)層進行處理。大部分業務應用均可以被當作一系列事務。
Transaction Script的特色是簡單容易理解,面向過程設計。 若是應用相對簡單,在應用的生命週期裏不會有基礎設施技術的改變,尤爲是業務邏輯不多會變更,採用Transaction Script風格簡單天然,性能良好,容易理解。
Transaction Script的缺點在於,對於複雜的業務邏輯難以保持良好的設計,事務之間的冗餘代碼不斷增多。應用架構容易出現「胖服務層」和「貧血的領域模型」。同時,Service層積聚愈來愈多的業務邏輯,致使可維護性和擴展性變差
領域模型屬於面向對象設計,領域模型具有本身的屬性行爲和狀態,領域對象元素之間經過聚合配合解決實際業務應用。可複用,可維護,易擴展,能夠採用合適的設計模型進行詳細設計。缺點是相對複雜,要求設計人員有良好的抽象能力。
TransactionScript風格業務邏輯主要在Service中實現,而在領域驅動設計的架構裏,Service只負責協調並委派業務邏輯給領域對象進行處理。所以,咱們能夠考察這一點來識別系統是Transaction Script架構仍是Domain Model架構。在實踐中,設計良好的領域設計架構在開發過程當中也容易向Transaction Script架構演變。
Domain:
Domain層是整個系統的核心層,該層維護一個使用面向對象技術實現的領域模型,幾乎所有的業務邏輯會在該層實現。Domain層包含Entity(實體)、ValueObject(值對象)、Domain Event(領域事件)和Repository(倉儲)等多種重要的領域組件。
Infrastructure:
Infrastructure(基礎設施層)爲Interfaces、Application和Domain三層提供支撐。全部與具體平臺、框架相關的實現會在Infrastructure中提供,避免三層特別是Domain層摻雜進這些實現,從而「污染」領域模型。Infrastructure中最多見的一類設施是對象持久化的具體實現。
層(Layers)被視爲構成應用或服務的水平堆疊的一組邏輯上的組件。它們幫助區分完成不一樣任務的組件,提供一個最大化複用和可維護性的設計。簡言之,是關於在架構方面應用關注點分離的原則。
在傳統的多層架構中,每一個解決方案的組件必須分隔到不一樣的層。每層的組件必須內聚並且有大約相同的抽象級別。每一個一級層應該和其餘的一級層鬆耦合。
從最底層的抽象級別看,例如第1層。這是系統的基礎層。這些抽象的步驟是一步一步的最後到最頂層。
多層應用的關鍵在於對依賴的管理。傳統的多層架構,層內的組件只能和同級或者低級層的組件交互。這有利於減小不一樣層內組件的依賴。一般有兩種多層架構的設計方法:嚴格和靈活的。
* 「嚴格的層設計」限定層內的組件只能和同一層、或者下一層的組件通訊。即第N層只能和第N-1層交互,N-1層只能和N-2層交互,等等。
* 「靈活的層設計」容許層內的組件和任何低級別層交互。這種設計中,第N層能夠和N-1,N-2層交互。
這種設計因爲不須要對其餘層進行重複的調用,從而能夠提升性能。然而,這種設計不提供層之間的同層隔離級別,使得它難以在不影響多個高級層的時候替換一個低級的層。
因爲層之間是經過定義明確的接口進行交互這一事實,很容易爲各層添加替代的實現(例如 Mock and Stubs)。
由於高層的組件只能和底層的交互,在單獨的組件上進行測試是很容易的。
使用層的好處
- 功能容易肯定位置,解決方案也就容易維護。層內高內聚,層間鬆耦合使得維護/組合層更容易。
- 其餘的解決方案能夠重用由不一樣層暴露的功能。
- 當項目按邏輯分層時,分佈式的部署更容易實現。
- 把層分佈到不一樣的物理層能夠提升可伸縮性;而後這一步應該進行仔細的評估,由於可能對性能帶來負面影響。
面向領域架構的分層:
在面向領域架構中,關鍵是要清楚界定和分離領域模型層和其他的層。
通常適合結合使用scrum(適用於項目管理)和XP(適用於軟件開發目標)方法對處理DDD實施項目。敏捷方法注重於交付商業價值,而DDD側重於結合軟件系統和業務模型。此 外,就DDD迭代的特性來講,SCRUM或DSDM這樣的敏捷方法對項目管理來講也是更好的框架。
DDD迭代週期的項目管理模型如圖所示。
本圖根據《Domain Driven Design and Development In Practice》一文中插圖進行了部分修改。
領域建模結束時能夠開始領域驅動設計。關於如何開始實現領域對象模型,Ramnivas Laddad推薦以下的步驟。他強調要更側重於領域模型中的領域對象,而不是服務。
* 從領域實體和領域邏輯開始。
* 不要一開始就從服務層開始,只添加那些邏輯不屬於任何領域實體或值對象的服務。
* 利用通用語言、契約式設計(DbC)、自動化測試、 CI和重構,使實現儘量地與領域模型緊密結合。
設計領域模型的通常步驟:
1. 根據需求創建一個初步的領域模型,識別出一些明顯的領域概念以及它們的關聯,關聯能夠暫時沒有方向但須要有(1:1,1:N,M:N)這些關係;能夠用文字精確的沒有歧義的描述出每一個領域概念的涵義以及包含的主要信息;
2. 分析主要的軟件應用程序功能,識別出主要的應用層的類;這樣有助於及早發現哪些是應用層的職責,哪些是領域層的職責;
3. 進一步分析領域模型,識別出哪些是實體,哪些是值對象,哪些是領域服務;
4. 分析關聯,經過對業務的更深刻分析以及各類軟件設計原則及性能方面的權衡,明確關聯的方向或者去掉一些不須要的關聯;
5. 找出聚合邊界及聚合根,這是一件頗有難度的事情;由於你在分析的過程當中每每會碰到不少模棱兩可的難以清晰判斷的選擇問題,因此,須要咱們平時一些分析經驗的積累才能找出正確的聚合根;
6. 爲聚合根配備倉儲,通常狀況下是爲一個聚合分配一個倉儲,此時只要設計好倉儲的接口便可;
7. 走查場景,肯定咱們設計的領域模型可以有效地解決業務需求;
8. 考慮如何建立領域實體或值對象,是經過工廠仍是直接經過構造函數;
9. 停下來重構模型。尋找模型中以爲有些疑問或者是蹩腳的地方,好比思考一些對象應該經過關聯導航獲得仍是應該從倉儲獲取?聚合設計的是否正確?考慮模型的性能怎樣,等等;
領域建模是一個不斷重構,持續完善模型的過程,你們會在討論中將變化的部分反映到模型中,從而是模型不斷細化並朝正確的方向走。
從設計和實現的角度來看,典型的DDD框架應該支持如下特徵。
* 應該是一個以POJO爲基礎的架構。
* 應該支持使用DDD概念的業務領域模型的設計和實現。
* 應該支持像依賴注入(DI)和麪向方向編程(AOP)這些概念的開箱即用。
* 與單元測試框架整合。
* 與其它Java/Java EE框架進行良好的集成,好比JPA、Hibernate、TopLink等。
一些反模式:
* 貧血的領域對象
* 重複的DAO
* 肥服務層:服務類在這裏最終會包含全部的業務邏輯。
* 依戀情結(Feature Envy):函數對某個類的興趣高過對本身所處類的興趣。
1. 創建完整自封閉的領域模型。
領域驅動架構相對比較容易理解,但創建一個完整自封閉的領域模型卻很困難。「領域模型」是一個針對業務邏輯抽象的分析模型,它反映出對領域問題的總體描述。領域模型不是編程的實現模型,而是一組抽象概念的集合。一個領域概念不必定映射成一個類,也有可能會映射不少的類(包括多個實體或值對象)。領域需求的最初細節,在功能層面經過領域專家的討論得出。領域專家並不必定須要熟知軟件開發領域的知識,相反強調的是具備領域中的相關知識。領域需求在相互討論中不斷獲得細化,還有可能在開發過程出現需求的反覆或變動,這都要求領域模型的創建完善是一個反覆重構的過程。敏捷開發是一種應對快速變化的需求的一種軟件開發能力。強調程序員團隊與業務專家之間的緊密協做、面對面的溝通(認爲比書面的文檔更有效)、頻繁交付新的軟件版本、緊湊而自我組織型的團隊、可以很好地適應需求變化的代碼編寫和團隊組織方法。故採用敏捷開發有利於領域模型的創建完善,以更能符合用戶的實際需求。
關於領域模型分析存在有多種分析方法。也許並非能常常能有機會去實踐這些分析方法或分析領域模型。但關於領域驅動架構的理解,有助於幫助咱們去理解領域驅動的設計,實現一些高內聚、低耦合的代碼實現。
2. 領域服務建模
創建和識別領域服務也比較容易出錯。一般的SSH分層架構與領域驅動架構相近,而SSH架構開發更容易致使Transaction Script架構而非領域驅動架構。在SSH分層架構上,開發人員更容易創建」貧血」模型,而在Service裏實現業務邏輯。而DDD強調「充血模型」,「薄」Service層。創建領域服務須要識別出領域業務邏輯,並將業務實現到領域模型中。一方面,業務需求充滿着變化,在開發過程當中難以把握。當業務不明需求不清時,「貧血模型」就更容易被人接受。另外一方面,在構建領域模型時,ORM映射顯示十分重要而且也很是複雜,包括類繼承體系與數據庫的映射,抓取策略和緩存管理在內的一系列問題等.「貧血模型」有時會簡化這種映射關係,同時,在處理對象依賴關係上顯得更加靈活性。而領域模型強調了領域邊界,對領域對象的訪問老是經過聚合根開始,在有時候,模型的某些遍歷會帶來更大的性能和穩定性上的問題。而解決這些問題時,又經常會從實效性上出發而犧牲模型個別的清晰性和純潔性。
3.領域對象、領域服務以及Repository之間的互相依賴
在實際開發中,開發人員會常常須要處理領域對象之間的依賴關係,以及領域對象與Repository間的依賴。一般可能的方案是讓service或Façade直接調用Repository,從而得到返回的領域對象。這種方式致使各層間的依賴,一般咱們應該考慮解耦這種依賴。當前實現組件解耦經常使用的技術無非是:控制反轉(IoC)、依賴注入(DI)、面向方面編程(AOP)以及分佈式服務接口。所以,解決依賴的一種思路利用DI或AOP將Repository和服務注入到領域對象中。Spring框架提供了相關的機制。在Spring環境中,利用Spring實例化對象,注入依賴,將服務、Repository等領域對象聯繫起來。
還有一種思路是採用事件驅動架構,經過Domain Event異步消息機制來實現。
4.架構不能保證領域驅動設計的貫徹與執行
領域模型的創建完善是一個反覆重構的過程。重構自己須要改變或調整應用代碼,但不修改應用已有功能或行爲。重構能夠是設計相關的,也能夠是代碼相關的。設計重構就是爲了避免斷完善模型、重構代碼來提高領域模型。在代碼重構時,因爲各方面的緣由,如需求變動、開發人員對領域驅動的理解以及對需求的理解,都有可能致使「胖服務」「貧血模型」等反模式的出現。在每次重構過程當中都有出現這種趨勢。如沒有嚴格的規定,對重構代碼的審查,以及可靠的測試(包括單元測試、自動代測試)。代碼的變化都無沒保證領域驅動設計的貫徹與執行。所以進行重構應該結合使用單元測試、自動化測試,同時的代碼審查,以確保代碼變更不會破壞已有的功能或改變原有的行爲模式,同時嚴格實現創建的領域模型。
參考:Domain Driven Design and Development In Practice