|
英文原文:Domain Driven Design and Development In Practice
背景
領域驅動設計(DDD)的中心內容是如何將業(yè)務領域概念映射到軟件工件中。大部分關于此主題的著作和文章都以Eric Evans 的書《領域驅動設計》為基礎,主要從概念和設計的角度探討領域建模和設計情況。這些著作討論實體、值對象、服務等DDD的主要內 容,或者談論通用語言、界定的上下文(Bounded Context)和防護層(Anti-Corruption Layer)這些的概念?!?/p>
本文旨在從實踐的角度探討領域建模和設計,涉及如何著手處理領域模型并實際地實現(xiàn)它。我們將著眼于技術主管和架構師在 實現(xiàn)過程中能用到的指導方針、最佳實踐、框架及工具。領域驅動設計和開發(fā)也受一些架構、設計、實現(xiàn)方面的影響,比如:
- 業(yè)務規(guī)則
- 持久化
- 緩存
- 事務管理
- 安全
- 代碼生成
- 測試驅動開發(fā)
- 重構
本文討論這些不同的因素在項目實施的整個生命周期中怎樣對其產生影響,還有架構師在實現(xiàn)成功的DDD中應該去尋求什么。 我會先列出領域模型應該具備的典型特征,以及何時在企業(yè)中使用領域模型(相對于根本不使用領域模型,或使用貧血的領域模型來說 )。
文章包括一個貸款處理示例應用,來演示如何將設計立場、以及這里討論的開發(fā)最佳實踐,應用在真實的領域驅動開發(fā)項目之 中。示例應用用了一些框架去實現(xiàn)貸款處理領域模型,比如Spring、Dozer、Spring Security、JAXB、Arid POJOs和Spring Dynamic Modules。示例代碼用Java編寫,但對大多數(shù)開發(fā)人員來說,不論語言背景如何,代碼都是很容易理解的。
引言
領域模型帶來了一些好處,其中有:
- 有助于團隊創(chuàng)建一個業(yè)務部門與IT部門都能理解的通用模型,并用該模型來溝通業(yè)務需求、數(shù)據(jù)實體、過程模型。
- 模型是模塊化、可擴展、易于維護的,同時設計還反映了業(yè)務模型。
- 提高了業(yè)務領域對象的可重用性和可測性。
反過來,如果IT團隊在開發(fā)大中型企業(yè)軟件應用時不遵循領域模型方法,我們看看會發(fā)生些什么。
不投放資源去建立和開發(fā)領域模型,會導致應用架構出現(xiàn)“肥服務層”和“貧血的領域模型”,在 這樣的架構中,外觀類(通常是無狀態(tài)會話Bean)開始積聚越來越多的業(yè)務邏輯,而領域對象則成為只有getter和setter方法的數(shù)據(jù) 載體。這種做法還會導致領域特定業(yè)務邏輯和規(guī)則散布于多個的外觀類中(有些情況下還會出現(xiàn)重復的邏輯)。
在大多數(shù)情況下,貧血的領域模型沒有成本效益;它們不會給公司帶來超越其它公司的競爭優(yōu)勢,因為在這種架構里要實現(xiàn)業(yè) 務需求變更,開發(fā)并部署到生產環(huán)境中去要花費太長的時間。
在考慮DDD實現(xiàn)的項目中各種架構和設計因素之前,讓我們先看看富領域模型的特性:
- 領域模型應該側重于具體的業(yè)務操作領域。它應該結合業(yè)務模型、策略和業(yè)務流程。
- 它應該與業(yè)務中的其它領域,還有應用架構中的其它層隔離開來。
- 它應該可重用,以避免相同的核心業(yè)務領域元素有任何重復的模型和實現(xiàn)。
- 模型應該設計得與應用中的其它層松耦合,這意味著領域層與上下兩層(即數(shù)據(jù)庫和外觀類)都沒有依賴關系。
- 它應當是一個抽象的、清晰劃分的層次,以使維護、測試、版本處理更容易??稍谌萜魍猓◤腎DE中)對領域類進行單元測試。
- 它應該用POJO編程模型來設計,沒有任何技術或框架依賴性(我總是告訴公司里我工作的項目團隊,我們軟件開發(fā)用的技術是 Java)。
- 領域模型應該獨立于持久化實現(xiàn)的細節(jié)(盡管技術確實會對模型有一些限制)。
- 它應該最小程度地依賴于任何基礎設施框架,因為它將比這些框架更經久,我們也不希望與任何外部框架緊耦合。
為了實現(xiàn)軟件開發(fā)中更高的投資回報率(ROI),業(yè)務單位和IT的高級管理人員必須在業(yè)務領域建模及其實現(xiàn)的投資上(時間 、金錢和資源)全力以赴。讓我們來看看實現(xiàn)領域模型需要的其它因素。
- 團隊應該經常接近業(yè)務領域主題專家。
- IT團隊(建模者、架構師和開發(fā)人員)應具備良好的建模、設計技能。
- 分析師應該具有良好的業(yè)務流程建模技能。
- 架構師和開發(fā)人員應該有豐富的面向對象設計(OOD)和編程(OOP)經驗。
領域驅動設計在企業(yè)架構中的作用
領域建模和DDD在企業(yè)架構(EA)中發(fā)揮著重要的作用。因為EA的目標之一就是結合IT和業(yè)務部門,業(yè)務實體的代表 ——領域模型就是EA的核心部分。這就是為什么大多數(shù)EA組件(業(yè)務或基礎設施)應該圍繞領域模型設計和實現(xiàn)的原 因。
領域驅動設計和SOA
面向服務的體系架構(SOA)最近幫助團隊構建基于業(yè)務流程的軟件構件和服務、加速新產品上市時間的勢頭越來越強勁。領 域驅動設計是SOA的一個關鍵因素,因為它有助于封裝領域對象中的業(yè)務邏輯和規(guī)則。領域模型也提供了定義服務契約使用的語言和上 下文。
如果還沒有領域模型,SOA的實行就應該包括領域模型的設計和實現(xiàn)。如果我們太過強調SOA服務、忽略了領域模型的重要性 ,那我們在應用架構中最終得到的就是一個貧血的領域模型和臃腫的服務。
理想的情況是,在開發(fā)應用層和SOA組件的同時,迭代地實現(xiàn)DDD,因為應用層和SOA組件都是領域模型要素的直接消費者。 使用豐富的領域實現(xiàn),通過給領域對象提供一個殼(代理),SOA設計將變得相對簡單。但如果我們太過于關注SOA層,在后端卻沒有 一個像樣的領域模型,業(yè)務服務就會調用不完整的領域模 型,這可能會導致出現(xiàn)一個脆弱的SOA架構。
項目管理
領域建模項目通常包括以下步驟:
- 首先為業(yè)務流程建模并文檔化。
- 選擇一個候選的業(yè)務流程,與業(yè)務領域專家一起使用通用語言來文檔化業(yè)務流程。
- 識別候選業(yè)務流程需要的所有服務。這些服務本質上可以是原子的(單步的)或組合好的(多步的,有無工作流皆可)。它們也可 以是業(yè)務(比如承?;蛸Y金)或基礎設施(比如電子郵件或工作調度)。
- 對上一步識別的服務所使用的對象,確定并文檔化其狀態(tài)和行為。
一開始關注業(yè)務領域核心元素的時候,就將模型保持在高水平是非常重要的。
從項目管理的觀點來看,真實的DDD實現(xiàn)項目和其它軟件開發(fā)項目所包含的階段是一樣的。這些階段包括:
- 對領域進行建模
- 設計
- 開發(fā)
- 單元測試和集成測試
- 基于設計和開發(fā)來完善、重構領域模型(模型概念的持續(xù)集成(CI))。
- 使用更新的領域模型重復上述步驟(領域實現(xiàn)的CI)。
非常適合在這里使用敏捷軟件開發(fā)方法學,因為敏捷方法注重于交付商業(yè)價值,恰好DDD側重于結合軟件系統(tǒng)和業(yè)務模型。此 外,就DDD迭代的特性來說,SCRUM或DSDM這樣的敏捷方法對項目管理來說也是更好的框架。結合使用SCRUM(適用于項目管理) 和XP(適用于軟件開發(fā)目標)方法對處理 DDD實現(xiàn)項目來說非常好。
DDD迭代周期的項目管理模型如圖1所示。
圖1. DDD迭代 周期圖(點擊查看大圖)
領域建模結束時可以開始領域驅動設計。關于如何開始實現(xiàn)領域對象模型,Ramnivas Laddad推薦如下的步驟。他強調要更側重于領域模型中的領域對象,而不是服務。
- 從領域實體和領域邏輯開始。
- 不要一開始就從服務層開始,只添加那些邏輯不屬于任何領域實體或值對象的服務。
- 利用通用語言、契約式設計(DbC)、自動化測試、 CI和重構,使實現(xiàn)盡可能地與領域模型緊密結合。
從設計和實現(xiàn)的角度來看,典型的DDD框架應該支持以下特征。
- 應該是一個以POJO(如果你的公司以.NET為主營,就是POCO)為基礎的架構。
- 應該支持使用DDD概念的業(yè)務領域模型的設計和實現(xiàn)。
- 應該支持像依賴注入(DI)和面向方向編程(AOP)這些概念的開箱即用。(注:稍后將在文章中詳細解釋這些概念)。
- 與單元測試框架整合,比如JUnit、TestNG 、Unitils等。
- 與其它Java/Java EE框架進行良好的集成,比如JPA、Hibernate、TopLink等。
示例應用
本文中使用的示例應用是一個住房貸款處理系統(tǒng),業(yè)務用例是批準住房貸款(抵押)的資金申請。將貸款申請?zhí)峤唤o抵押放貸 公司的時候,首先要通過承保過程,承保人在這一過程中根據(jù)客戶的收入詳情、信用歷史記錄和其它因素來決定批準還是拒絕貸款請求 。如果貸款申請獲得承保組的批準, 就進入貸款審批程序的結清和融資步驟。
貸款處理系統(tǒng)中的融資模塊自動給貸款人支付資金。通常,融資過程從抵押放貸公司(通常是銀行)將貸款包遞交給產權公司 開始。接著產權公司評估貸款包,并與房產買賣雙方一起確定結清貸款的時間。貸款人和賣方與結算中介在產權公司會面、簽署書面協(xié) 議,來轉移房產產權。
架構
典型的企業(yè)應用架構由下面四個概念上的層組成:
- 用戶界面(表現(xiàn)層):負責給用戶展示信息,并解釋用戶命令。
- 應用層:該層協(xié)調應用程序的活動。不包括任何業(yè)務邏輯,不保存業(yè)務對象的狀態(tài),但能保存應用程序任務 過程的狀態(tài)。
- 領域層:這一層包括業(yè)務領域的信息。業(yè)務對象的狀態(tài)在這里保存。業(yè)務對象的持久化和它們的狀態(tài)可能會 委托給基礎設施層。
- 基礎設施層:對其它層來說,這一層是一個支持性的庫。它提供層之間的信息傳遞,實現(xiàn)業(yè)務對象的持久化 ,包含對用戶界面層的支持性庫等。
讓我們更詳細地看一下應用層和領域層。
應用層:
- 負責應用中UI屏幕之間的導航,以及與其它系統(tǒng)應用層之間的交互。
- 還能對用戶輸入的數(shù)據(jù)進行基本(非業(yè)務相關)的驗證,然后再把數(shù)據(jù)傳到應用的其它層(更底層)。
- 不包含任何業(yè)務、領域相關的邏輯、或數(shù)據(jù)訪問邏輯。
- 沒有任何反映商業(yè)用例的狀態(tài),但卻能處理用戶會話或任務進展的狀態(tài)。
領域層:
- 負責業(yè)務領域的概念,業(yè)務用例和業(yè)務規(guī)則的相關信息。領域對象封裝了業(yè)務實體的狀態(tài)和行為。貸款處理應用中的業(yè)務實體例子 有抵押(Mortgage)、房產(Property)和貸款人(Borrower)。
- 如果用例跨越多個用戶請求(比如貸款登記過程包含多個步驟:用戶輸入貸款詳細信息,系統(tǒng)基于貸款特性返回產品和利率,用戶 選擇特定的產品/利率組合,最后系統(tǒng)會用這個利率鎖定貸款),還可以管理業(yè)務用例的狀態(tài)(會話)。
- 包含服務對象,這些服務對象只包含一個定義好的、不屬于任何領域對象的可操作行為。服務封裝了業(yè)務領域的狀態(tài),而業(yè)務領域 并不適用于領域對象本身。
- 是商業(yè)應用的核心,應該與應用的其它層隔離開來。而且,它不應該依賴于其它層使用的應用框架(JSP/JSF、Struts、EJB、Hibernate、XMLBeans等)。
下面的圖2顯示了應用中使用的不同架構層次,以及它們與DDD有怎樣的關系。
下面的設計觀點被認為是目前DDD實現(xiàn)訣竅的主要部分:
OOP是領域實現(xiàn)中最重要的基本原則。應該利用像繼承、封裝和多態(tài)這樣的OOP概念,使用Plain Java類和接口來設計領域 對象。大部分領域元素是既有狀態(tài)(屬性)又有行為(操作狀態(tài)的方法或操作)的真正對象。它們同時對應于真實世界的概念,能很合 適地適用于OOP概念。DDD中的實體和值對象都是OOP概念的典型例子,因為它們同時有狀態(tài)和行為。
在典型的工作單元(Unit of Work)中,領域對象需要與其它的對象協(xié)作,無論這些對象是服務、資源庫、還是工廠。領域對 象還需要處理其它那些本身就橫切的關注點, 比如領域狀態(tài)變化跟蹤、審計、緩存、事務管理(包括事務重試)。這些都是可重用、非 領域相關的關注點,通常很容易在包括領域層的整個代碼中散布和重復。在領域對象中嵌入該邏輯會導致領域層和非領域相關的代碼互 相糾纏、產生混亂。
說到處理對象間之沒有緊耦合的代碼依賴關系和隔離橫切關注點的時候,OOP并不能獨自為領域驅動設計和開發(fā)提供極好的設 計解決方案。在這是可以利用DI和AOP這樣的設計概念對OOP進行補充,以盡量減少緊耦合、提高模塊化、更好地處理橫切關注點。
依賴注入
DI能很有效地將配置和依賴代碼從領域對象中移出。此外,領域類對數(shù)據(jù)訪問對象(DAO)類、服務類對領域類的設計依賴性 使得DI成為DDD實現(xiàn)中“必須有”的內容。通過將資源庫和服務之類的其它對象注入到領域對象,DI有助于創(chuàng)建一個更 清晰、松耦合的設計。
在示例應用中,服務對象(FundingServiceImpl)利用DI注入實體對象(Loan、Borrower和FundingRequest)。實體 也通過DI引用資源庫。同樣的,像數(shù)據(jù)源 、Hibernate會話工 廠和事務管理器 這些其它的Java EE資源也被注入到服務和資源庫對象中。
面向方面編程
通過從領域對象中移除橫切關注點代碼,比如檢查、領域狀態(tài)變化跟蹤等,AOP有助于實現(xiàn)一個更好的設計(即在領域模型中 少一些亂七八糟的內容)??衫?AOP把協(xié)同對象和服務注入領域對象,特別是那些容器沒有實例化的對象(比如持久化對象)。在可 以利用AOP的領域層中,其它的方面有緩存、事務管理和基于角色的安全(授權)。
貸款處理應用利用自定義方面將數(shù)據(jù)緩存引入服務對象。貸款產品和利率信息從數(shù)據(jù)庫表中加載一次(客戶端第一次請求這些 信息時),然后存儲到適用于后面產品和利率查找的對象緩存(JBossCache)中。產品和利率會被頻繁訪問,但不會定期更新,所以緩存數(shù) 據(jù)是一個很好的候選方案,而不是每次都從后端的數(shù)據(jù)庫獲取。
在近期的討論貼子里,DDD中DI和AOP概念的作用是主要的話題。討論以Ramnivas Laddad的演講為基礎,Ramnivas在 其演講中主張,沒有AOP和DI的幫助,DDD無法實現(xiàn)。 Ramnivas在這個演講中討論了“細粒度DI”的概念,這一概念利 用AOP使領域對象恢復機敏性。他說領域對象需要訪問其它細粒度的對象來提供豐富的 行為,該問題的解決方案是在領域對象中注入服 務、工廠或資源庫(通過在調用構造或setter方法時期使用方面來注入依賴)。
Chris Richardson也討論了有關利用DI、對象和方面,通過減少 耦合、提高模塊化來改進應用設計。Chris談到了“超級大服務”反模式,這是應用代碼耦合、混亂、分散的結果,他還 談了如何利用DI和AOP的概念來避免這一反模式。
注解(Annotations)
最近定義、處理方面和DI的趨勢是使用注解。對實現(xiàn)遠程服務(比如EJB或Web Services)來說,注解有助于減少所需的工 件。它們還簡化了配置管理任務。Spring 2.5、Hibernate 3,以及其它框架都充分利用注解在 Java企業(yè)應用的不同層中配置組件。
我們應該利用注解生成模板代碼,模板代碼能在靈活性上增加價值。但同時應該謹慎使用注解。注解應該用于不會引起混淆或 誤解實際代碼的地方。使用注解的一個很好的例子是Hibernate ORM映射,注解能直接用類或屬性名給指定的SQL表或列名添加值。 另一方面,像JDBC驅動配置(驅動類名、JDBC URL、用戶名和密碼)這樣的詳細信息則更適合于存放在XML文件中,而不是使用注解 。這基于數(shù)據(jù)庫在同一個上下文中這一假設。如果領域模型和數(shù)據(jù)庫表之間需要相當多的轉換,那就應該好好思考一下設計了。
Java EE 5提供JPA注解,比如@Entity、@PersistenceUnit、 @PersistenceContext< /a>等,以此給簡單的Java類添加持久化細節(jié)。在領域建模上下文中,實體、資源庫和服務都是使用注解的好地方。
@Configurable是Spring將資源庫和服務注入領域對象的方式。Spring框架在@Configurable注解之上擴 展了“領域對象依賴注入”思想。 Ramnivas最近在博客中談論了即將發(fā)布的Spring 2.5.2版本(從項目的Snapshot Build 379開始可用)的最新改進。 有三個新的方面(AnnotationBeanConfigurerASPect、 AbstractInterfaceDrivenDependencyInjectionASPect和 AbstractDependencyInjectionASPect)為領域對象依賴注入提供 了簡單、更靈活的選擇。Ramnivas說,引入中間的方 面(AbstractInterfaceDrivenDependencyInjectionASPect),其主要原 因是要讓領域特定的注解和接口發(fā)揮 作用。Spring還提供了其它注解來幫助設計領域對象,比如 @Repository、@S ervice和@Transactional。
示例應用中使用了部分注解。實體對象(Loan、Borrower和FundingRequest)使用了@Entity注解;這些對象還使用 @Configurable注解綁定資源庫對象;服務類也使用@Transactional注解來用事務行為裝飾服務方法。
領域模型和安全
領域層的應用安全確保只有授權的客戶端(人類用戶或其它應用)能調用領域操作,訪問領域狀態(tài)。
Spring安全(Spring Portfolio的一個子項目)同時為應用的表現(xiàn)層(以URL為基礎)和領域層(方法級)提供了細粒度的訪問控制。該框架使用Spring的 Bean Proxy來攔截方法調用,運用安全約束。它為使用MethodSecurityInterceptor類的Java對象提供了基于角色的聲明式安全。它也有針對領域對象的訪問控制列表(ACL's) 形式的實例級別安全,以控制實例級別的用戶訪問。
在領域模型中使用Spring安全來處理授權需求的主要好處是,框架有一個非侵入式的架構,我們可以完全隔離領域和安全方面 。此外,業(yè)務對象也不會和安全實現(xiàn)細節(jié)混成一團。我們可以只在一個地方編寫通用的安全規(guī)則,(使用AOP技術)在任何需要實現(xiàn)它 們的地方運用它們。
在領域和服務類中,授權在類方法調用級別進行處理。舉例來說,對于高達一百萬美元的貸款,承保領域對象中的“貸 款審批”方法可以由任何具有“承保人”角色的用戶調用;而對于超過一百萬美元的貸款申請來說,同一領域對象 中的審批方法則只能由具有“核保主管”角色的用戶調用。
表1. 各個應用層中的安全關注點
層 | 安全關注點 |
---|---|
客戶端/控制器 | 認證、Web頁面(URL)界別授權 |
外觀 | 基于角色的授權 |
領域 | 領域實例級別授權、ACL |
數(shù)據(jù)庫 | DB對象級別授權(存儲過程、存儲函數(shù)、觸發(fā)器) |
業(yè)務規(guī)則
業(yè)務規(guī)則是業(yè)務領域中的重要部分。它們定義了數(shù)據(jù)驗證和其它的約束規(guī)則,這些規(guī)則需要應用于特定業(yè)務流程場景中的領域 對象。業(yè)務規(guī)則通常分為下面幾類:
- 數(shù)據(jù)驗證
- 數(shù)據(jù)轉換
- 商業(yè)決策
- 流程流向(工作流邏輯)
上下文在DDD世界中非常重要。上下文的特性決定了領域對象協(xié)作及其它運行時因素,比如運用什么業(yè)務規(guī)則等。驗證以及其 它業(yè)務規(guī)則往往都是在一個特定的業(yè)務上下文中處理的。這意味著,相同的領域對象在不同的業(yè)務上下文中將不得不處理不同的一組業(yè) 務規(guī)則。比如說,通過了貸款審批流程中的承保步驟后,貸款領域對象的一些屬性(像貸款數(shù)額和利率)就不能再改變了。但在貸款剛 剛登記并與特定利率關聯(lián)的時候,同樣的屬性是可以改變的。
盡管所有的領域特定業(yè)務規(guī)則都應該封裝在領域層,但一些應用設計將規(guī)則放在了外觀類中,這導致了領域類在業(yè)務規(guī)則邏輯 方面變成了“貧血的”。在小型應用中這可能是可接受的 解決方案,但不推薦將其用于包含復雜業(yè)務規(guī)則的中大型企業(yè)應 用。更好的設計方案是把規(guī)則放在它們應該在的地方——領域對象中。如果一個業(yè)務規(guī)則 跨越兩個或兩個以上的實體對 象,那么該規(guī)則應該做為服務類的一部分。
此外,如果我們不在應用中下苦功,往往把業(yè)務規(guī)則變成代碼里的一串switch語句。隨著規(guī)則變得越來越復雜,開發(fā)人員不會 愿意花費時間去重構代碼,將switch語句移到更易于管理的設計中。在類中硬編碼復雜的流向或決策規(guī)則邏輯會導致類中出現(xiàn)更長的方 法、代碼重復、最終僵化的應用設計,長遠來看,這將成為維護的噩夢。一個良好的設計是把所有的規(guī)則(特別是隨著業(yè)務策略的變化 而頻繁改變的復雜規(guī)則)放到規(guī)則引擎(利用規(guī)則框架,比如JBoss Rules、 OpenRules或Mandarax )中去,并從領域類中進行調用。
驗證規(guī)則通常會用不同的語言實現(xiàn),比如Javascript、XML、Java代碼,還有其它腳本語言。但由于業(yè)務規(guī)則的動態(tài)特性, Ruby、Groovy、領域特定語言(DSL) 這些腳本語言是定 義、管理這些規(guī)則更好的選擇。Struts(應用層)、Spring(服務層)和Hibernate(ORM)都有其自己的驗證模塊,我們可以在這 些驗證模塊中對傳入或傳出的數(shù)據(jù)對象運用驗證規(guī)則。在一些情況下,驗證規(guī)則還能被處理為方面,它們可以組合到應用的不同層次中 去(比如服務和控制器)。
在編寫領域類處理業(yè)務規(guī)則時,緊記單元測試方面是非常重要的。規(guī)則邏輯中的任何變化都應該很容易、獨立地單元可測。
示例應用包括一個業(yè)務規(guī)則集來驗證貸款特性是否都在允許的產品和利率規(guī)格內。規(guī)則在腳本語言中(Groovy)進行定義,并 用于傳遞給FundingService對象的貸款數(shù)據(jù)。
設計
從設計的角度出發(fā),領域層應該有一個定義清晰的邊界,以避免來自非核心領域層關注點的層的損壞,比如特定供應商的說明 、數(shù)據(jù)過濾、轉換等。領域元素應該設計為正確地保存領域狀態(tài)和行為。不同的領域元素會基于狀態(tài)和行為進行不同的結構化。下面的 表2展示了領域元素及其包含的內容。
表2. 領域元素及其狀態(tài)和行為
領域元素 | 狀態(tài)/行為 |
---|---|
實體、值對象、聚合 | 狀態(tài)和行為都有 |
數(shù)據(jù)傳輸對象 | 只有狀態(tài) |
服務、資源庫 | 只有行為 |
同時包含狀態(tài)(數(shù)據(jù))和行為(操作)的實體、值對象、聚合應該有定義清晰的狀態(tài)和行為。同時,該行為不應該超出對象邊 界的范圍。實體應該在作用于本地狀態(tài)的用例中完成大部分工作。但它們不應該知道太多無關的概念。
對那些封裝領域對象狀態(tài)所需要的屬性來說,好的設計實踐是只包括這些屬性的getter/setter方法。設計領域對象時,只為 那些能改變的屬性提供setter方法。此外,公有的構造函數(shù)應該只含有必需的屬性,而不是包含領域類中所有的屬性。
在大部分用例中,我們并不是真的要去直接改變對象的狀態(tài)。所以,代替改變內部狀態(tài)的做法是,創(chuàng)建一個帶有已改變狀態(tài)的 新對象并返回該新對象。這種方法在這些用例中就足夠了,還能降低設計的復雜性。
聚合類對調用者隱藏了協(xié)作類的用法。聚合類可用來封裝領域類中復雜的、有侵入性的、狀態(tài)依賴的需求。
支持DDD的設計模式
有幾種有助于領域驅動設計和開發(fā)的設計模式。下面是這些設計模式的列表:
- 領域對象(DO)
- 數(shù)據(jù)傳輸對象(DTO)
- DTO組裝器
- 倉儲(Repositories):資源庫包含領域為中心的方法,并使用DAO與數(shù)據(jù)庫交互。
- 泛型DAO
- 時態(tài)模式(Temporal Patterns):這些模式給豐富的領域模型添加了時間維。Bitemporal框架基于Martin Fowler的時態(tài)模式,為處理領域模型中的雙時態(tài)問題提供了 設計方法。核心的領域對象及其雙時態(tài)屬性能用ORM產品持久化,比如Hibernate。
在DDD中應用的其它設計模式還包括策略模式、外觀模式和工廠模式。Jimmy Nilsson在他的書里討論了工廠模式,認為它是一種領域模式。
DDD反模式
在最佳實踐和設計模式的反面,架構師和開發(fā)人員在實現(xiàn)領域模型時還應該提防一些DDD的壞氣味。由于這些反模式,領域層 在應用架構中成為最不重要的部分,外觀類反而在模型中承擔了更重要的責任。下面是一些反模式:
- 貧血的領域對象
- 重復的DAO
- 肥服務層:服務類在這里最終會包含所有的業(yè)務邏輯。
- 依戀情結(Feature Envy):這是Martin Fowler在他關于重構的書中提到的典型的壞氣味,在該反模式中,一個類的方 法對屬于其它類的數(shù)據(jù)太過念念不忘。
數(shù)據(jù)訪問對象
DAO和Repository在領域驅動設計中都很重要。DAO是關系型數(shù)據(jù)庫和應用之間的契約。它封裝了Web應用中的數(shù)據(jù)庫 CRUD操作細節(jié)。另一方面,Repository是一個獨立的抽象,它與DAO進行交互,并提供到領域模型的“業(yè)務接口”。
Repository使用領域的通用語言,處理所有必要的DAO,并使用領域理解的語言提供對領域模型的數(shù)據(jù)訪問服務。
DAO方法是細粒度的,更接近數(shù)據(jù)庫,而Repository方法的粒度粗一些,而且更接近領域。此外,一個Repository類中能注 入多個DAO。Repository和DAO能防止解耦的領域模型去處理數(shù)據(jù)訪問和持久化細節(jié)。
領域對象應該只依賴于Repository接口。這就是為什么是注入Repository、而不是DAO會產生一個更規(guī)則的領域模型的原因 。DAO類不能由客戶端(服務和其它的消費者類)直接調用??蛻舳藨撌冀K調用領域對象,領域對象再調用DAO將數(shù)據(jù)持久化到數(shù)據(jù) 存儲中。
處理領域對象之間的依賴關系(比如實體及其Repository之間的依賴關系)是開發(fā)人員經常遇到的典型問題。解決這個問題通 常的設計方案是讓服務類或外觀類直接調用Repository,在調用Repository的時候返回實體對象給客戶端。該設計最終導致前面提到 的貧血領域模型,其中外觀類會開始堆積更多的業(yè)務邏輯,而領域對象則成為單純的數(shù)據(jù)載體。好的設計是利用DI和AOP技術將 Repository和服務注入到領域對象中去。
示例應用在實現(xiàn)貸款處理領域模型時遵循了這些設計原則。
持久化
持久化是一個基礎設施方面,領域層應該與其解耦。JPA通過對類隱藏持久化實現(xiàn)的細節(jié),提供了這一抽象。它由注解 (annotation)推動,所以不需要XML映射文件。但同時,表名和列名嵌在代碼中,在某些情況下可能并不是一個靈活的解決辦法。
使用提供數(shù)據(jù)網格解決方案的網格計算產品,比如Oracle的Coherence、WebSphere的Object Grid、GigASPaces,開發(fā)人員在建模和設計業(yè)務領域時,完全不需要考慮 RDBMS。數(shù)據(jù)庫層用內存對象/數(shù)據(jù)網格的形式從領域層抽象出來。
緩存
在我們討論領域層的狀態(tài)(數(shù)據(jù))時,我們不得不談到緩存問題。經常訪問的領域數(shù)據(jù)(比如抵押貸款處理應用中的產品和利 率)很值得緩存起來。緩存能提高性能,減少數(shù)據(jù)庫服務器的負載。服務層很適合緩存領域狀態(tài)。TopLink和Hibernate這些ORM框架也提供數(shù)據(jù)緩存。
貸款處理示例應用使用JBossCache框架來緩存產品和利率詳情,以減少數(shù)據(jù)庫調用、提高應用性能。
事務管理
對保持數(shù)據(jù)完整性、整體提交或回滾UOW(工作單元模式)來說,事務管理是很重要的。應該在應用架構層的哪里處理事務一 直存在爭議。交叉實體的事務(在同一UOW中跨越多個領域對象)也影響在哪里處理事務這一設計決策。
一些開發(fā)人員傾向于在DAO類中管理事務,這是一個欠佳的設計。該設計導致過細粒度的事務控制,對那些事務跨越多個領域 對象的用例來說,這種事務控制沒有靈活性。服務類應該處理事務;即使事務跨越多個領域對象,服務類也能處理事務,因為在大多數(shù) 用例中,是服務類在處理控制流。
示例應用中的FundingServiceImpl類處理資金申請的事務,通過調用資源庫執(zhí)行多個數(shù)據(jù)庫操作,并在單一事務中提交或回 滾所有的數(shù)據(jù)庫變化。
數(shù)據(jù)傳輸對象
領域對象模型在結構上與從業(yè)務服務接收或發(fā)送的消息不兼容,在這樣一種SOA環(huán)境中,DTO就是設計中很重要的一部分。消 息通常都在XML模式定義文檔 (XSD)中定義和維護,從XSD編寫(或代碼生成)DTO對象,并在領域和SOA服務層之間使用它們來 傳輸數(shù)據(jù)(消息)是一種普遍的做法。在分布式應用中,將來自于一個或多個領域對象中的數(shù)據(jù)映射到DTO中會成為必然的弊端,因為 從性能和安全角度出發(fā),跨越網絡發(fā)送領域對象是不實際的。
從DDD的角度來看,DTO還有利于維護服務層和UI層之間的縫隙,其中DO用于領域層和服務層,DTO用于表現(xiàn)層。
Dozer框架用于將一或多個領域對象組裝為一個DTO對象。它是雙向的 ,將領域對象轉換為DTO的時候,它會保存大量備用的代碼和時限,反之亦然。DO和DTO之間的雙向映射有利于消除“DO到 DTO”和“DTO到DO”各自的轉換邏輯。該框架還能正確處理類型和數(shù)組的轉換。
示例應用在資金處理申請到來時,利用Dozer映射文件(XML)將FundingRequestDTO對象劃分成為Loan、Borrower、 FundingRequest實體對象。在返回給客戶端時,映射同樣負責將來自實體的資金響應數(shù)據(jù)聚合到單一的DTO對象中。
DDD實現(xiàn)框架
像Spring、Real Object Oriented(ROO)、Hibernate和Dozer這些框架都有助于設計并實現(xiàn)領域模型。支持DDD實 現(xiàn)的其它框架有Naked Objects、Ruby On Rails、Grails,以及 Spring Modules XT Framework。
Spring負責實例化,并將服務、工廠和資源庫這些領域類聯(lián)接在一起。它還使用@Configurable注解將服務注入實體。該注 解是Spring特有的,所以完成這一注入的其它選擇是使用諸如Hibernate攔截器的東西。
ROO是建立在觀點“領域第一,基礎設施第二”之上的DDD實現(xiàn)框架。開發(fā)該框架是為了減少Web應用開發(fā)中 模式的模板編碼。利用ROO時,我們定義領域模型,接著框架(基于Maven Archetypes)為模型-視圖-控制器(MVC)、DTO、業(yè)務層外觀和 DAO層生成代碼。它也能為單元測試和集成測試生成stubs。
ROO有幾個非常實用的實現(xiàn)模式。比如說,它區(qū)分處理屬性的狀態(tài)、使用屬性級訪問的持久層、只反映必需屬性的公有構造函 數(shù)。
開發(fā)
沒有實際的實現(xiàn),模型就沒有用處。實現(xiàn)階段應該盡可能多地自動化完成開發(fā)任務。為了看看什么任務能自動完成,讓我們看 看涉及領域模型的一個典型用例。下面是用例的步驟列表:
輸入請求:
- 客戶端調用外觀類,以XML文檔(XSD兼容的)的方式發(fā)送數(shù)據(jù);外觀類為UOW初始化一個新的事務。
- 驗證輸入的數(shù)據(jù)。驗證包括基本驗證(基本的/數(shù)據(jù)類型/屬性級檢查)和業(yè)務驗證。如果有任何的驗證錯誤,拋出適當?shù)漠惓!?/li>
- 將描述轉換為代碼(以成為簡單的領域)。
- 改變數(shù)據(jù)格式,以成為簡單的領域模型。
- 進行所有的屬性分割(比如,在客戶實體對象中,將客戶姓名分成名字和姓)。
- 把DTO拆分為一或多個領域對象。
- 持久化領域對象的狀態(tài)。
輸出響應:
- 從數(shù)據(jù)存儲中獲取領域對象的狀態(tài)。
- 如果必要,緩存狀態(tài)。
- 將領域對象組裝為對應用有利的數(shù)據(jù)對象(DTO)。
- 進行所有的數(shù)據(jù)元素合并或分離(比如結合名字和姓,組成單一的客戶姓名屬性)。
- 將代碼轉換為描述。
- 必要時改變數(shù)據(jù)格式,以處理客戶端數(shù)據(jù)使用的要求。
- 如果有必要,緩存DTO的狀態(tài)。
- 事務提交(如果有錯誤則回滾),退出控制流。
下表顯示了應用中不同的對象,這些對象將一個層的數(shù)據(jù)傳到另一個層。
表3. 應用層間的數(shù)據(jù)流向
層 | 起點對象 | 終點對象 | 框架 |
---|---|---|---|
DAO | 數(shù)據(jù)庫表 | DO | Hibernate |
領域委托 | DO | DTO | Dozer |
數(shù)據(jù)傳輸 | DTO | XML | JAXB |
正如你所看到的,相同的數(shù)據(jù)以不同形式(DO、DTO、XML等)在應用架構中傳遞的層并不多。大部分持有數(shù)據(jù)的這些對象 (Java或XML),還有像 DAO、DAOImpl、DAOTest這些類實際上都是基礎設施。這些有模板代碼和結構的類、XML文件都很適合 代碼生成。
代碼生成
ROO這樣的框架還為新項目創(chuàng)建了一個標準、一致的項目模板(使用Maven插件)。使用預先生成的項目模板,我們可以實 現(xiàn)目錄結構的一致性,其中存放源碼、測試類、配置文件,以及對內部和外部(第三方)組件庫的依賴關系。
典型的企業(yè)軟件應用所需的種種類和配置文件時,其數(shù)量之多令人望而生畏。代碼生成是解決該問題的最好辦法。代碼生成工 具通常使用某類模板框架來定義模板,或是代碼生成器能從中生成代碼的映射。Eclipse建模框架(EMF)的幾個子項目有助于Web應用項目需要的各種 工件的代碼生成。模型驅動架構(MDA)工具,比如AndroMDA,都利用EMF在架構模型的基礎上生成代碼。
說到在領域層編寫委托類,我看到開發(fā)人員手動編寫這些類(大多是從無到有地寫完第一個,接著用“復制并粘貼 ”的模式來為其它的領域對象創(chuàng)建所需的委托 類)。由于這些類大部分都是領域類的外觀,它們很適合代碼生成。代碼生成是長 遠的解決辦法,盡管建立并測試代碼生成器(引擎)增加了初期的投入(代碼量和 時間)。
對生成的測試類來說,一個好的選擇就是在需要進行單元測試的主類中,為帶有復雜業(yè)務邏輯的方法創(chuàng)建抽象方法。這樣,開 發(fā)人員能繼承生成的測試基類,然后實現(xiàn)不能自動生成的自定義業(yè)務邏輯。同樣,這個方法也適用于任何有不能自動創(chuàng)建測試邏輯的測 試方法。
對編寫代碼生成器來說,腳本語言是一個更好的選擇,因為它們開銷少,還支持模板創(chuàng)建和自定義選項。如果我們在DDD項目 中充分利用代碼生成,我們只需要從無到有地編寫少量的代碼。必須從無到有進行創(chuàng)建的工件有:
- XSD
- 領域對象
- 服務
一旦我們定義了XSD和Java類,我們可以生成下列全部或大部分的類和配置文件:
- DAO接口和實現(xiàn)類
- 工廠
- 資源庫
- 領域代理(如果有必要)
- 外觀(包括EJB和WebService類)
- DTO
- 上述類的單元測試(包括測試類和測試數(shù)據(jù))
- Spring配置文件
表4列出了Web應用架構中不同的層,以及那些層中能生成什么工件(Java類或XML文件)。
表4. DDD實現(xiàn)項目中的代碼生成
層/功能 | 模式 | 你寫的代碼 | 生成的代碼 | 框架 |
---|---|---|---|---|
數(shù)據(jù)訪問 | DAO/資源庫 | DAO接口, DAO實現(xiàn)類, DAOTest, 測試種子數(shù)據(jù) | Unitils, DBUnit | |
領域 | DO | 領域類 | DomainTest | |
持久化 | ORM | 領域類 | ORM映射, ORM映射測試 | Hibernate, ORMUnit |
數(shù)據(jù)傳輸 | DTO | XSD | DTO | JAXB |
DTO組裝 | 組裝 | 映射 | DO-DTO映射文件 | Dozer |
委托 | 業(yè)務委托 | DO到DTO的轉換代碼 | ||
外觀 | 外觀 | 遠程服務, EJB, Web Service | ||
控制器 | MVC | 控制器映射文件 | Struts/Spring MVC | |
表示層 | MVC | 視圖配置文件 | Spring MVC |
委托層是唯一同時理解領域對象和DTO的層。其它層,例如持久層,不應該察覺到DTO。
重構
重構就是改變或調整應用代碼,但不修改應用的功能或行為。重構可以是設計相關的,也可以是代碼相關的。設計重構是為了 不斷完善模型、重構代碼來提升領域模型。
由于重構的迭代性和領域建模不斷演進的性質,重構在DDD項目中發(fā)揮著重要作用。將重構任務集成到項目中的方法之一是在 項目的每次迭代中添加重構環(huán)節(jié),重構結束之后才算完成迭代。理想情況下,每項開發(fā)任務之前和之后都應該進行重構。
進行重構應該有嚴格的規(guī)定。結合使用重構、CI和單元測試,以確保代碼變化不會破壞任何功能,同時,代碼的變化要有助于 以后的代碼和性能改進。
自動化測試在重構應用代碼中發(fā)揮著至關重要的作用。沒有良好的自動化測試和測試驅動開發(fā)(TDD)實踐,重構可能會產生反面的效果,因為沒有自動化的方式去驗證作為重構 一部分的設計和代碼并變化沒有改變行為、或破壞功能。
像Eclipse這樣的工具有助于用迭代的方式和作為開發(fā)一部分的重構來實現(xiàn)領 域模型。Eclipse有一些功能,比如把一個方法提取或移動到不同的類中,或將一個方法下推 到子類中。也有幾個Eclipse代碼分析插件 有助于處理代碼依賴關系、識別DDD反模式。我做項目的設計和代碼審查時,都是依靠插件JDepend、Classycle和Metrics 來評估應用中領域和其它模塊的質量。
Chris Richardson談到運用代碼重構,以 使用Eclipse提供的重構功能將過程設計轉變?yōu)橐粋€OO設計。
單元測試/持續(xù)集成
我們剛才談到的目標之一是領域類應該(在最初的開發(fā)階段,以及隨后重構已有代碼時)單元可測,而不過多依賴于容器或其 它基礎設施代碼。TDD方法有助于團隊盡早地找出任何設計問題,并有助于驗證代碼與領域模型在保持一致。DDD對測試先行開發(fā)來說 是很理想的,因為狀態(tài)和行為都包含在領域類中,而且單獨測試它們應該是容易的。測試領域模型的狀態(tài)和行為,又不太過關注于數(shù)據(jù) 訪問或持久化的實現(xiàn)細節(jié)是很重要的。
單元測試框架,比如JUnit或TestNG,都是實現(xiàn)和處理領域模型很棒的工具。其它測試框架,像DBUnit和Unitils,也可用來測試領域層,尤其是把測試數(shù)據(jù)注入到DAO類中。對在單元測 試類中增加測試數(shù)據(jù)來說,這將大大減少編寫額外的代碼。
模擬對象(Mock objects)同樣有利于單獨測試領域對象。但是在領域層不要濫用模擬對象是很重要的。如果有其他測試領 域類的簡單方法,你應該使用這些方法來代替使用模擬對象。比如說,如果你能使用真實的后端DAO類(而不是模擬的DAO實現(xiàn))和內 存HSQL數(shù)據(jù)庫(而不是真實的數(shù)據(jù)庫)測試一個實體類,能使領域層單元測試運行得更快,而運行得更快正好是使用模擬對象潛在的主 要想法。這樣,你將能測試領域對象之間的協(xié)作(交互),以及它們之間交換的狀態(tài)(數(shù)據(jù))。使用模擬對象,我們則只能測試領域對 象之間的交互。
一旦開發(fā)任務完成,所有在開發(fā)階段創(chuàng)建的單元測試和集成測試(不管有沒有使用TDD做法)都將成為自動化測試套件的一部 分。這些測試用應該經常進行維護,并經常在本地或更高一級的開發(fā)環(huán)境中執(zhí)行,以便找出新的代碼變化是否在領域類中引入了Bug。
Eric Evans在他的書中提到了CI,他說 CI應該始終運用在界定的上下文中,應該包括人和代碼的同步。像CruiseControl和Hudson這些CI工具可用來建立一個自動化構建和測試的環(huán)境,來運行應用構建腳本 (使用Ant或Maven這些構建工具創(chuàng)建)從SCM倉庫中(像CVS、Subversion等)檢出代 碼,編譯領域類(以及應用中的其它類),并在沒有構建錯誤的情況下自動運行所有的測試(單元測試和集成測試)。CI工具還可以設 置在有任何構建或測試錯誤時(通過E-mail或RSS Feeds)通知項目團隊。
部署
領域模型絕對不會是靜態(tài)的;在項目生命周期中,它們會隨著業(yè)務需求的演變、新項目中新需求的提出而發(fā)生變化。此外,隨 著你開發(fā)和實現(xiàn)領域模型,你能不斷學習和提高,而且你也想在已有的模型中運用新的知識。
打包、部署領域類的時候,隔離很關鍵。因為領域層依賴于DAO層的一面,而服務外觀層又依賴于DAO層的另一面(參見圖2- 應用架構圖),所以這些領域類打包、部署為一或多個模塊來處理依賴關系很有意義。
DI、AOP和工廠這些設計模式在設計階段減少了對象之間的耦合,并使應用模塊化;OSGi(以前被稱為開放服務網關規(guī)范)則在運行時處理模塊化。OSGi正在 成為打包、發(fā)布企業(yè)應用的標準機制。它能很好地處理模塊之間的依賴關系。我們還能用OSGi來進行領域模型的版本處理。
我們可以把DAO類打包到一個OSGi的Bundle(DAO Bundle)中,把服務外觀類打包到另一個Bundle(服務Bundle)中, 所以DAO或服務實現(xiàn)進行了修改,或是部署了應用的不同版本,由于 OSGi,應用都不需要重啟。如果我們?yōu)榱讼蚝蠹嫒?,必須支持?些領域對象已有的版本和新的版本,那我們也可以部署相同領域類的兩個不同版本。
為了利用OSGi的能力,應用對象在消費之前(即在客戶端能查找到它們之前),應該在OSGi平臺中進行注冊。這意味著我們 必須使用OSGi的API進行注冊,我們還必須處理使用OSGi容器啟動和通知服務時的失敗場景。Spring Dynamic Modules框架對該領域很有利,它允許在應用中導出或 導入任何對象類型,而不改變任何代碼。
Spring DM還提供測試類,以在容器外運行OSGi集成測試。比如說,能從IDE中直接用AbstractOsgiTests運行集成測試。設置由測試基礎設施來處理,所以我們不需要為測試編寫MANIFEST.MF文件,或者進 行任何的打包或部署。該框架支持大部分目前可用的OSGi實現(xiàn)(Equinox、 Knopflerfish和Apache Felix)。
貸款處理應用使用OSGi、Spring DM、Equinox容器來處理模塊級別的依賴關系,以及領域和其它模塊的部署。 LoanAppDeploymentTests說明了Spring DM測試模塊的用法。
示例應用設計
在貸款處理示例應用中用到的領域類列舉如下:
實體:
- Loan
- Borrower
- UnderwritingDecision
- FundingRequest
值對象:
- ProductRate
- State
服務:
- FundingService
倉儲(Repository):
- LoanRepository
- BorrowerRepository
- FundingRepository
圖3展示了示例應用的領域模型圖。
在本文中討論的大部分DDD設計概念和技術都在示例應用中進行了運用。像DI、AOP、注解、領域級別安全、持久化這些概念 都用到了。另外,我還使用了幾個開源框架來助力DDD開發(fā)和實現(xiàn)任務。這些框架列舉如下:
- Spring
- Dozer
- Spring安全
- JAXB(用于封送處理和取消封送處理數(shù)據(jù)的Spring-WS)
- Spring Testing(用于單元測試和集成測試)
- DBUnit
- Spring Dynamic Modules
示例應用中的領域類利用Equinox和Spring DM框架部署為OSGi模塊。下表顯示了示例應用的模塊打包細節(jié)。
表5. 打包、部署細節(jié)
層 | 部署工件名稱 | 模塊內容 | Spring配置文件 |
---|---|---|---|
客戶端/控制器 | loanapp-controller.jar | 控制器,客戶端代理類 | LoanAppContext-Controller.xml |
外觀 | loanapp-service.jar | 外觀(遠程)服務,服務代理類,XSD | LoanAppContext-RemoteServices.xml |
領域 | loanapp-domain.jar | 領域類、DAO,通用的DTO | LoanAppContext-Domain.xml, LoanAppContext-Persistence.xml |
框架 | loanapp-framework.jar | 框架,實用工具,監(jiān)視(JMX)類,方面 | LoanAppContext-Framework.xml, LoanAppContext-Monitoring.xml, LoanApp- ASPects.xml |
結論
DDD是一個功能強大的概念,只要團隊接受了DDD的培訓,并開始運用“領域第一,基礎設施第二”的觀點, 它就會改變建模者、架構師、開發(fā)人員和測試人員思考軟件的方式。由于領域建模、設計和實現(xiàn)中會涉及具有不同背景和專長領域的不 同利益相關方(來自IT和業(yè)務單位),引用Eric Evans的說法,“不要弄混設計觀點(DDD)和有助于我們完成它的技術工具箱 (OOP、DI、AOP)之間的界限”。
前進中的新領域
本節(jié)涵蓋了一些新出現(xiàn)的、影響DDD設計和開發(fā)的方法。這些概念中的一些仍在不斷發(fā)展,觀察它們將如何影響DDD也很有意 思。
在領域模型標準的治理、策略實施,以及實現(xiàn)的最佳實踐中,實施Architecture Rules和契約式設計起到了重要作用。 Ramnivas談到了利用ASPects來強制僅通過工廠創(chuàng)建Repository對象;這是在設計領域 層時經常被違背的規(guī)則。
領域特定語言(DSL)和業(yè)務自然語言(BNL)近幾年來正得到越來越多的關注。人們可以在領域類中使用這些語言表達業(yè)務 邏輯。BNL可以用來保存業(yè)務規(guī) 范,記錄業(yè)務規(guī)則,還能作為可執(zhí)行代碼,從這種意義上來說,BNL是非常強大的。還能用它們創(chuàng)建測 試用例,來驗證系統(tǒng)是否如預期的那樣運轉。
行為驅動開發(fā)(BDD) 是最近被討論的另一個有趣概念。通過提供跨越 業(yè)務和技術之間鴻溝的通用詞匯(通用語言),BDD有利于將開發(fā)集中在有優(yōu)先次序、可驗證的商業(yè)價值的發(fā)布上。通過利用側重于系 統(tǒng)行為方面的術語,而不是單單著眼于測試,BDD引導開發(fā)人員將TDD背后的真正價值最大程度地發(fā)揮出來。如果正確實踐的話,BDD 可以成為DDD很好的補充,BDD概念會對領域對象的開發(fā)產生積極的影響;畢竟領域對象就是對狀態(tài)和行為的封裝。
事件驅動的體系架構(EDA) 是能在領 域驅動設計中發(fā)揮作用的另一個領域。比如說,在領域對象實例中通知任何狀態(tài)變化的事件模型將有助于處理后事件(post-event)處 理任務, 在領域對象的狀態(tài)改變時,后事件處理任務就需要被觸發(fā)。EDA有利于封裝基于事件的邏輯,將之嵌進領域邏輯的核心。 Martin Fowler評述了領域事件設計模式。
資源
- 領域驅動設計:軟件核心復雜性應對之道》,Evans Eric著,Addison-Wesley出版社
- 《領域驅動設計和模式應用》,Jimmy Nilsson著,Addison-Wesley出版社
- 《重構到模式》,Joshua Kerievsky著,Addison-Wesley出版社
- 沒有依賴注入和面向方面編程,能很好地進行領域驅動設計 嗎?
英文原文:Domain Driven Design and Development In Practice (譯者:王麗娟)
it知識庫:領域驅動設計和開發(fā)實戰(zhàn),轉載需保留來源!
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。