本章大部分內容摘自:《領域驅動設計:軟體核心複雜性應對之道》一書中的第四章,分離領域,純屬原創。如有錯誤請指正,相互學習。 在軟體中,專門用於解決問題的那部分通常之占整個軟體的系統的很小一部分,這與其重要性遠遠不成比例。要想實現最佳的設計構思,就得去研究模型中的元素並它們視為一個系統 模式:LAYE... ...
本章大部分內容摘自:《領域驅動設計:軟體核心複雜性應對之道》一書中的第四章,分離領域,純屬原創。如有錯誤請指正,相互學習。
在軟體中,專門用於解決問題的那部分通常之占整個軟體的系統的很小一部分,這與其重要性遠遠不成比例。要想實現最佳的設計構思,就得去研究模型中的元素並它們視為一個系統
模式:LAYERED ARCHITECTURE (分層結構)
在面向對象的程式中,常常會在業務對象中直接寫入用戶界面、資料庫訪問等支持代碼。而一些額外的業務邏輯則會被嵌入到用戶界面組件和資料庫腳本的行為中。這麼做是為了以最簡單的方式在短期內完成開發工作。
如果與領域有關的代碼大量分散在大量的其他代碼之中,那麼查看和分析領域代碼就會變得相當困難。對用戶界面的簡單修改實際上很可能會改變業務邏輯,而要想調整業務規則也很可能需要對用戶界面代碼、資料庫操作代碼或者其他的程式元素進行仔細的篩查。這樣就不太可能實現一致的、模型驅動的對象了,同時也會給自動化測試帶來困難。程式中每一個活動都有其自身的邏輯和使用的技術,因此程式本身必須簡單明瞭,否則就會讓人無法理解。
要想創建能夠處理複雜任務的程式,需要把不同的關註點分開考慮,是設計中的每個部分都得到單獨的關註,在分離的同事,也需要維持系統內部複雜的交互關係。
分割軟體系統普遍採用LAYERED ARCHITECTURE(分層架構),有些層已成標準。基本原則是層中的任何元素都僅依賴於本層的其他元素或其下層的元素。向上通信必須通過間接的傳遞機制進行。分層的價值在於每一層只代表程式中的某一特定方面,這種限制使每個方面的設計都更具內聚性,更容易解釋。
大多數成功的架構使用都包括下麵四個概念層的某個版本:
- 用戶界面層(或表示層):負責用戶顯示信息和解釋用戶指令,這裡指的用戶是另一個電腦系統,不一定是使用用戶界面的人。
- 應用層:定義軟體要完成的任務,並指揮表達領域概念的對象來解決問題。這一層所負責的工作對業務來說意義重大,也是與其他系統的應用層進行交互的必要渠道。應用層要儘量簡單,不包含業務規則或者知識,而只為下一層中的領域對象協調任務,分配工作,使他們互相協作。它沒有反應業務情況的狀態,但是卻可以具有另外一種狀態,為用戶或程式顯示某個任務的進度。
- 領域層(或模型層):負責表達業務概念,業務狀態信息以及業務規則。儘管保存業務狀態的技術細節是有基礎設施層實現的,但是反應業務情況的狀態是有本層控制並且使用的,領域層是業務軟體的核心。
- 基礎設施層:為上面各層提供通用的技術能力:為應用層傳遞消息,為領域層提供持久化機制,為用戶界面層回執屏幕組件,等等。基礎設施層還能夠通過架構框架來支持四個層次間的交互模式。
給複雜的應用程式劃分層次,在每一層內分別進行設計,使其具有內聚性並且只依賴於它的下層。採用標準的架構模式,只與上層進行鬆散連接。將所有與領域相關的代碼放在一個層中,並把它與用戶界面層、應用層以及基礎設施層的代碼分開。領域對象應該將重點放在如何表達領域模型上,而不需要考慮自己的顯示和存儲問題,也無需管理應用任務等內容。這使得模型的含義足夠豐富,結構足夠清晰。
各層之間可以生成一個應用程式,也可以每層部署為一個應用,在分散式系統中每層可以靈活的放在不同的伺服器或者客戶端中。
將各層關聯起來
程式需要劃分層次,但是各層之間也需要互相連接,在連接各層的同事需要不影響分離帶來的好處。各層之間是鬆散連接的,層與層之間的以來關係只能是單向的,上層可以直接使用或操作下層元素。方法是保持對下層元素的引用,採用常規的交互手段調用下層元素的公共介面。如果下層需要與上層元素通信,則需要採用另一種通信機制,如使用架構模式來鏈接上下層,比如回調模式或觀察者模式。
鏈接用戶界面層與應用層有很多種方式,只要連接方式能夠維持領域層的獨立性,保證設計領域對象時不需要同時考慮可能與其交互的界面,那麼連接方式就可用。
一般基礎設施層只提供服務,如:發送郵件的消息發送功能,持久化操作功能、導出excel功能等,但有時有些技術組件被設計成直接支持其它層的基本功能,比如:為所有領域對象提供抽象基類等.
架構框架
整合了大量基礎設施需求的框架常會需要其他層以某種特定的方式實現,比如以框架類的子類形式或者帶有結構化的方法簽名。最好的框架既能解決複雜技術問題,也能讓領域開發人員集中精力去表達模型而不考慮其他問題。然而使用框架很容易為項目製造障礙,要麼設定了太多假設,減少領域設計的可選範圍;要麼是需要實現太多的東西,影響開發進度。
項目中一般都需要某種形式的架構框架(儘管有時項目團隊選擇了不太合適的框架)。當使用框架時,應該明確其使用目的,建立一種可以表達領域模型的實現並且用它來解決重要問題。項目團隊必須想方設法讓框架滿足這些需求,即使這意味著要拋棄框架中的一些功能。
模型屬於領域層
領域模型是一系列概念的幾核。“領域層”則是領域模型以及所有與其直接相關的設計元素的表現,它由業務邏輯的設計和實現組成。在MODEL-DRIVEN DESIGN中,領域層的軟體構造反應出了模型概念。
模式:THE SMART UI "ANTI-PATTERN"
如果一個經驗並不豐富的項目團隊要完成一個簡單的項目,要使用MODEL-DRIVEN DESIGN 以及 LAYERED ARCHITECTURE,那麼項目組將會經歷艱難的學習過程。掌握新技術、學習對象建模、對基礎設施及各層的管理工作使原本簡單的任務卻需要花費很長的時間來完成。簡單項目開發周期較短、期望值也不高,所以可能任務完成之前項目木就會被取消。即使項目有時間,沒有專家幫助,團隊成員也不太可能掌握這些技術。最後假如剋服了這些困難,恐怕也只會開發出一套簡單的系統,因為項目本來就不需要豐富的功能。
因此,當情況需要時:在用戶界面中實現所有業務邏輯。將應用程式分成小的功能模塊,分別將它們實現成用戶界面,併在其中嵌入業務規則。用關係資料庫作為共用的數據存儲庫。使用自動化程度最高的用戶界面創建工具和可用的可視化編程工具。
這也是一個有爭議的觀點,但是SMART UI有著自身的優勢,效率高、學習成本低等,但是一旦使用了SMART UI如果補充些代碼將無法在使用領域驅動設計。
項目團隊也經常犯一個錯誤就是採用了一種複雜的設計方法,卻無法保證項目從頭到尾始終使用它。另一種常見的也是代價高昂的錯誤則是為項目構建一種複雜的基礎設施,並使用頂級的工具,而這樣的項目根本不需要它們。
將領域設計與其他部分混在一起會產生災難性的後果,所以不要隨意使用多種模式。
Example:
項目分層結構
將應用程式分層,並對指定層隱藏類的可見度,使用maven的多模塊項目是個不錯的選擇,由於接觸尚少,尚不知還有沒有其他的辦法,如有請指點相互學習,下麵參考下koala項目的項目結構及編寫一個小demo。關於maven的知識我就不多說了,請大家自行搜索吧。首先看下目錄結構:
demo:為父項目,進行一些公共配置
demo-facade: 外觀層介面
demo-facade-impl:外觀層實現類,實現demo-facade層的介面
demo-application:應用層
demo-infra:基礎設施層
demo-conf:基礎設施層,主要是一些配置文件信息
demo-web:用戶界面層
demo-core:領域層
由於koala項目分層較多,其中外觀層在一些相對簡單一些的項目中可以去掉,因為外觀層主要是封裝各個子系統的細節來提供統一的介面對外發佈,可以繞過外觀層直接讓用戶界面層訪問應用層。
根據分層基本原則,本層依賴或操作本層及下層的元素,依賴是單向性的,koala項目的依賴方向如下:
demo-web項目為界面層,主要顯示用戶信息與解釋用戶指令,即封裝好參數然後傳遞給外觀層。引用其他項目的配置信息為:
<dependency><groupId>com.yunzaipiao</groupId><artifactId>demo-facade</artifactId><version>${project.parent.version}</version></dependency><dependency><groupId>com.yunzaipiao</groupId><artifactId>demo-conf</artifactId><version>1.0.0-SNAPSHOT</version></dependency><dependency><groupId>com.yunzaipiao</groupId><artifactId>demo-infra</artifactId><version>1.0.0-SNAPSHOT</version></dependency>
demo-facade與demo-facade-impl項目為外觀層,這一層為應用層上面的一層,對應用層進行了進一步的抽象,如果項目不是太複雜,沒有太多的子系統,可以沒有這一層。demo-facade為介面定義項目,一般不需要引用其他的項目,demo-facade-impl是外觀層的介面實現項目,需要引用下層項目,不過上面也說過,同層元素也是可以相互引用的,所以這裡也可以引用demo-facade項目,因為他們在同一層,只不過不是一個項目而已,下麵為demo-facade-impl引用其他兩個項目的配置信息:
<dependency><groupId>com.yunzaipiao</groupId><artifactId>demo-application</artifactId><version>${project.parent.version}</version></dependency><dependency><groupId>com.yunzaipiao</groupId><artifactId>demo-facade</artifactId><version>${project.parent.version}</version></dependency>
demo-application 為應用層,應用層定義要完成的任務和指揮領域層解決問題,不存儲業務規則,應用層可以引用下層項目及基礎設施層,配置信息如下:
<dependency><groupId>com.yunzaipiao</groupId><artifactId>demo-core</artifactId><version>${project.parent.version}</version></dependency><dependency><groupId>com.yunzaipiao</groupId><artifactId>demo-infra</artifactId><version>${project.parent.version}</version></dependency>
demo-core 為領域層項目,處理業務規則,可以引用基礎設施層,主要通過基礎設施層來進行持久化操作。由於一個基礎設施層可以有多個項目組成,這裡把持久化操作的相關內容都定義在了其他的項目中,引用的配置如下:
<dependency><groupId>org.dayatang.dddlib</groupId><artifactId>dddlib-query-channel</artifactId><exclusions><exclusion><groupId>org.hibernate</groupId><artifactId>hibernate-core</artifactId></exclusion><exclusion><groupId>org.javassist</groupId><artifactId>javassist</artifactId></exclusion></exclusions></dependency>
demo-infra 是基礎設施層項目,由於基礎設施層為項目的最層,所以一般不會對其它層進行直接操作,這裡也就沒有配置引用信息。
編寫應用層及領域層實例
現在模仿一個非常簡單的用戶註冊的例子,其中領域模型的getter setter方法都不再顯示了,還有一些其他的在此不重要的代碼都不寫了。在用戶註冊時,登錄用戶名與昵稱是不能重覆的,所以在註冊的時候需要驗證用戶的登錄用戶名與昵稱是否已經存在,驗證時的查詢資料庫等代碼也沒有實現,請簡單看下吧。
首先是領域模型,UserModel的代碼
package com.yunzaipiao.demo.core.domain;import org.openkoala.koala.commons.domain.KoalaAbstractEntity;import com.yunzaipiao.demo.core.exception.LoginNameExistsException;import com.yunzaipiao.demo.core.exception.NickNameExistsException;/**
* 用戶 Model* @created 2016年3月23日 下午9:13:35*/public class UserModel extends KoalaAbstractEntity {/**
* 登錄用戶名*/private String loginName;/**
* 密碼*/private String password;/**
* 昵稱*/private String nickName;/**
* 真實姓名*/private String realName;/**
* 判斷登錄用戶名是否存在** @created 2016年3月23日 下午9:12:04*/private void isExistsLoginName() {
if (true) {
throw new LoginNameExistsException("loginName is exists!");
}}/**
* 判斷昵稱是否存在** @auth 周志君* @created 2016年3月23日 下午9:12:25*/private void isExistsNickName() {
if (true) {
throw new NickNameExistsException("nickName is exists!");
}}public UserModel(String loginName, String password, String nickName,String realName) {isExistsLoginName();isExistsNickName();this.loginName = loginName;this.password = password;this.nickName = nickName;this.realName = realName;}// getter setter ....
}
在領域類中包含四個成員變數,以及三個方法,分別是:
isExistsNickName:這個方法用來判斷用戶的昵稱是否已存在,如果已存在則拋出異常
isExistsLoginName:這個方法用來判斷用戶登錄用戶名是否已存在,如果已存在則拋出異常
UserModel(String loginName, String password, String nickName, String realName):構造方法,該構造方法,首先調用上述的兩個方法判斷用戶昵稱及用戶登錄名是否已存在,如果已存在則繼續向上拋出異常有應用層進行處理。
下麵看看應用層的代碼,首先是介面只定義一個註冊的介面,如下;
package com.yunzaipiao.demo.application;public interface UserApplication {/**
* 註冊介面* @param loginName* @param password* @param nickName* @param realName* @created 2016年3月23日 下午9:24:20*/public void reg(String loginName, String password, String nickName, String realName);
}
下麵是介面實現類,實現類的方法中,只需要new一個UserModel,然後調用UserModel的save方法即可,save方法是UserModel繼承的KoalaAbstractEntity中的方法,可以將領域類持久化到資料庫中,代碼如下:
package com.yunzaipiao.demo.application.impl;import com.yunzaipiao.demo.application.UserApplication;import com.yunzaipiao.demo.core.domain.UserModel;public class UserApplicationImpl implements UserApplication {@Overridepublic void reg(String loginName, String password, String nickName,
String realName) {UserModel userModel = new UserModel(loginName, password, nickName, realName);userModel.save();}}
外觀類與界面類就不寫實例了,因為都是簡單的將參數封裝一下傳遞過來就可以了,一般沒有什麼業務可以處理的。這裡的介面類使用的傳遞多個參數的方式,也可以將參數封裝到一個DTO中,直接傳遞一個類過來,不顧這裡為了方便也沒有在生成其他的類。
不過有這裡簡單的代碼可以看出,應用層只是定義要完成的任務如用戶註冊。而具體如何註冊,有哪些業務規則,如登錄用戶名不能重覆,昵稱不能重覆等,則有領域類來進行處理。應用層定義任務,然後指揮者領域層進行完成任務,基礎設施層協助應用層或領域層。界面層則是處理用戶下達的指令並解釋成應用層可以理解的參數。
本章大部分內容摘自:《領域驅動設計:軟體核心複雜性應對之道》一書中的第四章,分離領域,純屬原創。如有錯誤請指正,相互學習。