在上一篇文章中,經過Spring Web應用的瑕疵引出改善的措施,咱們講解了領域驅動開發的相關概念和設計策略。本文主要講解領域模型的幾種類型和DDD的簡單實踐案例。html
在《實現領域驅動設計》一書中提到了幾種架構風格:六邊形架構、REST架構、CQRS 和事件驅動等。在實際使用中,落地的架構並不是是純粹其中的一種,而頗有可能戶將上述幾種架構風格結合起來實現。java
分層架構的一個重要原則是每層只能與位於其下方的層發生耦合。分層架構能夠簡單分爲兩種,即嚴格分層架構和鬆散分層架構。在嚴格分層架構中,某層只能與位於其直接下方的層發生耦合,而在鬆散分層架構中,則容許某層與它的任意下方層發生耦合。DDD分層架構中比較經典的三種模式:四層架構、五層架構和六邊形架構。程序員
Eric Evans在《領域驅動設計-軟件核心複雜性應對之道》這本書中提出了傳統的四層架構模式:web
傳統的四層架構都是限定型鬆散分層架構,即Infrastructure層的任意上層均可以訪問該層(「L」型),而其它層遵照嚴格分層架構。算法
五層架構是根據《DCI架構:面向對象編程的新構想》中說起的DCI架構模式總結而成。DCI架構(Data、Context和Interactive三層架構):編程
DCI目前普遍被看做是對DDD的一種發展和補充,用在基於面向對象的領域建模上。五層架構的具體定義以下:設計模式
六邊形架構(Hexagonal Architecture),又稱爲端口和適配器風格,最先由 Alistair Cockburn 提出。在 DDD 社區獲得了發展和推廣,之因此是六變形是爲了突顯這是個扁平的架構,每一個邊界的權重是相等的。ruby
咱們知道,經典分層架構分爲三層(展示層、應用層、數據訪問層),而對於六邊形架構,能夠分紅另外的三層:網絡
這樣作的好處是將使業務邊界更加清晰,從而得到更好的擴展性,除此以外,業務複雜度和技術複雜度分離,是 DDD 的重要基礎,核心的領域層能夠專一在業務邏輯而不用理會技術依賴,外部接口在被消費者調用的時候也不用去關心業務內部是如何實現。多線程
RESTful風格的架構將 資源
放在第一位,每一個 資源
都有一個 URI 與之對應,能夠將 資源
看着是 DDD 中的實體;RESTful 採用具備自描述功能的消息實現無狀態通訊,提升系統的可用性;至於 資源
的哪些屬性能夠公開出去,針對 資源
的操做,RESTful使用HTTP協議的已有方法來實現:GET、PUT、POST和DELETE。
在 DDD 的實現中,咱們能夠將對外的服務設計爲 RESTful 風格的服務,將實體/值對象/領域服務做爲資源
對外提供增刪改查服務。可是並不建議直接將實體暴露在外,一來實體的某些隱私屬性並不能對外暴露,二來某些資源獲取場景並非一個實體就能知足。所以咱們在實際實踐過程當中,在領域模型上增長了 DTO 這樣一個角色,DTO 能夠組合多個實體/值對象的資源對外暴露。
CQRS 就是日常你們在講的讀寫分離,一般讀寫分離的目的是爲了提升查詢性能,同時達到讀/寫的解耦。讓 DDD 和 CQRS 結合,咱們能夠分別對讀和寫建模,查詢模型一般是一種非規範化數據模型,它並不反映領域行爲,只是用於數據顯示;命令模型執行領域行爲,且在領域行爲執行完成後,想辦法通知到查詢模型。
那麼命令模型如何通知到查詢模型呢? 若是查詢模型和領域模型共享數據源,則能夠省卻這一步;若是沒有共用數據源,則能夠藉助於 消息模式
(Messaging Patterns)通知到查詢模型,從而達到最終一致性(Eventual Consistency)。
Martin 在 blog 中指出:CQRS 適用於極少數複雜的業務領域,若是不是很適合反而會增長複雜度;另外一個適用場景爲獲取高性能的服務。
在上面小節講解了領域驅動設計的幾種架構風格,下面咱們具體結合簡單的實例來看其中的領域模型劃分,初步分爲4大類:
咱們看看這些領域模型的具體內容,以及他們的優缺點。
失血模型簡單來講,就是domain object只有屬性的getter/setter方法的純數據類,全部的業務邏輯徹底由business object來完成(又稱TransactionScript),這種模型下的domain object被Martin Fowler稱之爲「貧血的domain object」。以下:
一個實體類叫作Item
public class Item implements Serializable {
private Long id = null;
private int version;
private String name;
private User seller;
// ...
// getter/setter方法省略不寫,避免篇幅太長
複製代碼
}
```
一個DAO接口類叫作ItemDao
public interface ItemDao {
public Item getItemById(Long id);
public Collection findAll();
public void updateItem(Item item);
複製代碼
} ```
一個DAO接口實現類叫作ItemDaoHibernateImpl
public class ItemDaoImpl implements ItemDao extends DaoSupport {
public Item getItemById(Long id) {
return (Item) getHibernateTemplate().load(Item.class, id);
}
public Collection findAll() {
return (List) getHibernateTemplate().find("from Item");
}
public void updateItem(Item item) {
getHibernateTemplate().update(item);
}
複製代碼
} ```
一個業務邏輯類叫作ItemManager(或者叫作ItemService)
複製代碼
public class ItemManager {
private ItemDao itemDao;
public void setItemDao(ItemDao itemDao) { this.itemDao = itemDao;}
public Bid loadItemById(Long id) {
itemDao.loadItemById(id);
}
public Collection listAllItems() {
return itemDao.findAll();
}
public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount,
Bid currentMaxBid, Bid currentMinBid) throws BusinessException {
if (currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0) {
throw new BusinessException("Bid too low.");
}
// ...
複製代碼
} ```
以上是一個完整的失血模型的示例代碼。在這個示例中,loadItemById、findAll 等等業務邏輯通通放在 ItemManager 中實現,而 Item 只有 getter/setter 方法。
簡單來講,就是 domain ojbect 包含了不依賴於持久化的領域邏輯,而那些依賴持久化的領域邏輯被分離到 Service 層。
Service(業務邏輯,事務封裝) --> DAO ---> domain object
這也就是 Martin Fowler 指的 rich domain object:這種模型的優勢:
缺點爲:
具體代碼較爲簡單,再也不展現。
充血模型和第二種模型差很少,所不一樣的就是如何劃分業務邏輯,即認爲,絕大多業務邏輯都應該被放在domain object裏面(包括持久化邏輯),而Service層應該是很薄的一層,僅僅封裝事務和少許邏輯,不和DAO層打交道。
Service(事務封裝) ---> domain object <---> DAO
這種模型就是把第二種模型的 domain object 和 business object 合二爲一了。因此 ItemManager 就不須要了,在這種模型下面,只有三個類,他們分別是:
在這種模型中,全部的業務邏輯所有都在Item中,事務管理也在Item中實現。 這種模型的優勢:
這種模型的缺點:
基於充血模型的第三個缺點,有同窗提出,乾脆取消Service層,只剩下domain object和DAO兩層,在domain object的domain logic上面封裝事務。
domain object(事務封裝,業務邏輯) <---> DAO
彷佛ruby on rails就是這種模型,他甚至把 domain object 和 DAO 都合併了。
該模型優勢:
該模型缺點:
在這四種模型當中,失血模型和脹血模型應該是不被提倡的。而貧血模型和充血模型從技術上來講,都已是可行的了。貧血模型和充血模型哪一個更加好一些?人們針對這個問題進行了曠日持久的爭論,最後仍然沒有什麼結果。雙方爭論的焦點主要在我上面加粗的兩句話上,就是領域模型是否要依賴持久層,由於依賴持久層就意味着單元測試的展開要更加困難(沒法脫離框架進行測試,原文的討論中這裏專指Hibernate),領域層就更難獨立,未來也更難從應用程序中剝離出來,固然好處是業務邏輯沒必要混放在不一樣的層中,使得單一職責性體現的更好。而支持者(充血模型)認爲,只要將持久層抽象出來,便可減小測試的困難性,同時適用充血模型畢竟帶來了很多開發上的便利性,除了依賴持久層這一點,擁有更多好處的充血模型仍然值得選擇。最後,誰也沒能說服誰,關於貧血模型和充血模型的選擇,更多的要靠具體的業務場景來決定,並不能說哪種更比哪種好。設計模式這種東西不是向來都沒有什麼定論麼。
我我的則傾向使用充血模型,由於充血模型更加像一個設計完善的系統架構,好在計算機世界裏有不少的 IOC 和 DI 框架,惟一的缺陷依賴持久層能夠經過各類變通的方法繞過,隨着技術的進步,一些缺陷也會被慢慢解決。個人思路是這樣的:先將持久層抽象爲接口,而後經過服務層將持久層注入到領域模型中,這樣領域模型僅僅會依賴於持久層的接口。而這個接口,能夠利用現有框架的技術進行抽象。