在目前的開發者社群,廣泛流行一種被Martin Fowler稱為貧血領域模型的構架模式。該模式由于大師的批判而飽受指責。這個模式有個緻命的缺陷:在處理複雜領域時常常表現不佳。很多迹象表明,當我們面對複雜應用時,最好還是轉向一個基于豐富領域模型的構架。
盡管豐富領域模型有着顯而易見的好處,但也給實踐帶來了挑戰,這既有建構技術上的原因,也有設計方法上的原因。對于建構技術,如Annotation、Aspect和DI等複雜技術的使用,最終能夠清晰的被掌握,但在設計方法上,往往由于實踐的不同而難以取得共識。
本文的目的僅僅是在技術上給出一種由貧血領域模型向豐富領域模型轉換的方案,供有相同需要的同行參考。
1.前提和限制<o:p></o:p>
無論使用哪種領域模型,通常都需要借助一些工具的支援,這些工具包括Ioc容器和O/R映射工具。因作者經驗所限,當提及這些工具時,隻意味着Java世界裡的Spring和Hibernate。
有關貧血領域模型的論述請參考:
http://www.martinfowler.com/bliki/AnemicDomainModel.html
貧血領域模型的最佳實踐請參考:
https://appfuse.dev.java.net
豐富領域模型的最權威指南請通路:
http://www.domaindrivendesign.org
由于語境的不同,不同的分層模式的術語具有不同的含義,但它們所要實作的任務,是可以分離出來的,而這些任務,目前還沒有造成廣泛的語義混亂。為了更好的比較本文中提及的兩種構架模式,将它們所要完成的任務定義如下:
任務 | 描述 |
表現邏輯 | 接收來自系統外部的請求,将請求代理到其他子產品,并将傳回的處理結果,通過某種方式呈現給請求者。 |
應用邏輯 | 是用例的外觀的實作,協調用例的真正實作者完成一次應用程式相關的功能。 |
領域邏輯 | 對問題領域最本質内容進行模組化,實作展現使用者核心價值的功能。 |
持久化邏輯 | 與資料的外部存儲互動。 |
基礎服務 | 更加的以技術為中心,為軟體系統的各子產品提供基礎支援。 |
2.貧血領域模型構架<o:p></o:p>
2.1.分層模式<o:p></o:p>
即使同樣打着貧血模型的标簽,它們也會有不同的風格。下面是比較典型的一種:
層 | 任務 | 對象 | 描述 |
表現層 | 表現邏輯 | 模型對象 | Model。領域層中的實體/值對象,也可以用獨立的對象。 |
視圖 | View | ||
控制器 | Controller。通過某種機制而獲得對使用者請求的響應。 | ||
領域層 | 在領域邏輯中混合了應用邏輯 | 服務 | Service。同時處理領域邏輯和應用邏輯 |
實體 | Entity。領域模型的靜态視圖 | ||
資料源層 | 持久化邏輯 | DAO對象 | 有很多實作方式,如JDBC、Hibernate、iBATIS、JPA、EJB CMP |
基礎設施層 | 基礎服務 | <o:p> </o:p> | <o:p> </o:p> |
可以參考下面的示意圖:[見附件1]
2.2.示例代碼<o:p></o:p>
現在假設有一個線上購物網站,我們要浏覽産品清單,然後選中了一個感興趣的産品,此時我們需要檢視該産品的詳細資訊。
1)頁面送出請求
下面是一個可能的JSF代碼片斷:
<h:commandlink value="#{product.name}" action="#{product.edit}"></h:commandlink> <f:param value="#{product.id}" name="productid"></f:param> |
2)分派到控制器
假設請求被排程到控制器yourpackage.action.ProductAction:
package yourpackage.action;<o:p></o:p> ......<o:p></o:p> public class ProductAction extends BaseAction {<o:p></o:p> //通過依賴注入的服務<o:p></o:p> private ProductService productService;<o:p></o:p> //作為模型對象的領域對象<o:p></o:p> private Product product;<o:p></o:p> private Integer id;<o:p></o:p> //響應檢視産品資訊的事件<o:p></o:p> public String edit() {<o:p></o:p> product = productService.getProduct(id);<o:p></o:p> return “product”; //JSF将其解析為product.xhtml視圖<o:p></o:p> }<o:p></o:p> } |
3)領域層的服務
一般會有一個服務接口ProductService,然後是該接口的實作ProductServiceImpl:
package yourpackage.service; ...... public class ProductServiceImpl extends BaseService implements ProductService { //通過依賴注入的DAO對象 private ProductDao productDao; //擷取産品資訊的領域邏輯方法 public Product getProduct(Integer id) { return productDao.getProduct(id); } } |
4)領域層的實體
下面是JPA風格的實體,該實體僅用于承載資料,而沒有領域行為(貧血之說由此而來)。
package yourpackage.model; ...... @Entity public class Product { @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="Id") public Long getId() { return id; } <o:p> </o:p> @Column(name="Name", length=30, nullable=false) public String getName() { return name; } } |
5)DAO對象
這裡同樣需要一個接口,其實作類如果用JPA的話:
package yourpackage.dao; ...... public class ProductDaoJpa extends BaseDaoJpa implements ProductDao { public Product getProduct(Integer id) { return (Product) getJpaTemplate().find(Product.class, id);; } ...... } |
2.3.分析<o:p></o:p>
優點:
* 獲得了分層的最基本的好處。
* 易于了解,快速掌握:沒有采用更複雜的技術,并且有豐富的示例資源。
* 有廣泛的工具支援,非常容易的從Spring/Hibernate類型的架構獲益。
* 适合于模型簡單,以CRUD操作為主的領域。
缺點:
* 模型的領域表達能力欠缺。
* 代碼職責配置設定不合理。
3. 豐富領域模型<o:p></o:p>
3.1分層模式<o:p></o:p>
典型的DDD風格的分層模式如下:
層 | 任務 | 對象 | 描述 |
表現層 | 表現邏輯 | 模型對象 | 領域層中的實體/值對象,也可以用獨立的對象。 |
視圖 | <o:p> </o:p> | ||
控制器 | <o:p> </o:p> | ||
應用層 | 應用邏輯 | 服務 | 對用例模組化 |
領域層 | 領域邏輯 | 服務 | 對領域操作模組化 |
實體 | 對領域概念模組化、并可被持久化 | ||
值對象 | Value Object。對領域概念模組化 | ||
存儲庫 | Repository。隔離持久化 | ||
基礎設施 | 持久化邏輯和基礎服務 | 資料映射 | 通常以DAO模式封裝持久化操作 |
基礎服務 |
可以參考下面的示意圖:[見附件2]
在實踐中,我個人更傾向于将資料映射從基礎設施層分離出來,這樣會有一個更清晰的層次結構,如下圖所示:[見附件3]
無論哪種領域模型,在是否将領域對象傳遞到表現層作為模型資料的問題上一直存在争議。要做出恰當的設計決定,需要在靈活性和嚴謹性之間做出平衡。有一些技術上的方法可以使領域對象在作為DTO對象進行傳遞時得到保護,可以參考下面的文章《Protecting the Domain Model》:
http://api.blogs.com/the_catch_blog/2005/05/protecting_the_.html
這些做法在一定程度上彌補了直接傳遞領域對象帶來的負面影響。
3.2有什麼不同?<o:p></o:p>
從上面的表格我們可以發現,要做的事還是那些事,隻不過部分職責被重新配置設定了而已。相對于貧血領域模型,在新的分層模式中:
* 不變的是表現層;
* 被合并的是資料源層,現在成了基礎設施的一部分;
* 增加的是應用層;
* 被重新組織的是領域層,其職責被配置設定到相應的對象中:實體、值對象、服務和存儲庫。
這裡最大的變化有三個:
1)應用邏輯被從領域邏輯中獨立出來,形成了新的應用層。該層的接口按照用例進行設計,是以粒度較大;該層反映的是用系統所實作的任務,如果需要,也能反映工作流程。
2)領域層隻反映領域邏輯中最核心部分,是以也是最複雜的(如果領域複雜程度超過技術複雜程度的話)。其中的實體不但持有資料,還具備豐富的行為;服務是一些領域相關的操作(這不同于應用層的服務,更不同于基礎設施層的服務);通過存儲庫隔離了與資料技術的聯系。
3)領域層可以獨立地通路其他層的服務和資源。這是一個有争議的話題,同時很有技術上的挑戰性,下文會通過執行個體代碼進行說明。