黃光芳, 金義富
(湛江師范學院 a.信息與教育技術中心;b.科技處,廣東湛江524048)
現代信息化高速發展的今天,越來越多的應用系統都被構建在Web之上,關于它的開發也經歷了面向過程、面向對象、面向服務架構(SOA)等開發過程,其復雜性也越來越高,使用的技術平臺有Java、.NET、Ruby等,目前基于Web的多層架構體系(如 J2EE、ASP.NET)已經成為解決企業級應用的主要途徑。因此,如何在軟件中更好地處理業務邏輯,且高質量高效率地完成軟件的開發便成為人們日益重視的問題。但是長期以來,傳統的Web平臺開發工作趨向于一種以技術為先導的開發方式,開發的過程即先從業務方面分析企業需求,然后把需求傳達給開發團隊,開發人員再依據需求的描述創造出最有可能的設想進行開發[1]。這些軟件開發的指導原則依然是基于數據庫設計而非面向對象設計,即開發人員一開始便根據需求建立數據庫模型,系統中的業務對象被機械化的數據庫CRUD操作代替,忽略面向對象的開發思想,缺少領域模型的開發,業務邏輯設計混亂,不能及時有效地反映用戶需求,開發的系統缺少通用性和科學性等。針對以上開發方法的不足,文中將領域驅動設計的開發思想融入到業務邏輯復雜的Web平臺的構建中,力求尋找一種更佳的企業級Web平臺的開發方案。
領域驅動設計(Domain-Driven Design,DDD)是領域驅動設計大師Eric Evans在2004年發表的文獻[1]中提出的軟件開發概念,是一種基于模型驅動開發(MDD)思想的嶄新的開發方式,目的是讓軟件系統在實現時準確的基于對真實業務過程的建模并根據真實業務過程的調整而調整。
領域驅動設計很好地遵循了關注點分離的原則,提出了成熟、清晰的分層架構,對領域對象進行了明確的策略和職責劃分,讓領域對象和現實世界中的業務形成良好的映射關系,為領域專家與開發人員搭建了溝通的橋梁。領域模型分為用戶界面層、應用層、領域層和基礎結構層四層,如圖1所示。

圖1 領域驅動設計的分層架構
用戶界面層。主要負責向用戶呈現信息、接受并解釋用戶命令,并把用戶的請求發送到應用層或領域層。
應用層。定義了系統要完成的工作,不包含業務邏輯的實現,只保留任務的進度狀態。
領域層。系統的核心,負責系統業務邏輯的實現工作,包含領域行為和模型。
基礎結構層。為上層提供通用的技術能力,持久化業務對象以及實現應用層的管理等。
領域驅動設計除了對系統架構進行了分層描述外,還對對象(Object)做了明確的職責和策略劃分,劃分的對象有實體、值對象、工廠、倉儲、服務、聚合等。
實體(Entities)。具備唯一ID,能夠被持久化,具備業務邏輯,對應現實世界業務對象。
值對象(Value objects)。不具有唯一ID,由對象的屬性描述,一般為內存中的臨時對象,可以用來傳遞參數或對實體進行補充描述。
工廠(Factories)。主要用來創建實體,目前架構實踐中一般采用IOC容器來實現工廠的功能。
倉儲(Repositories)。用來管理實體的集合,封裝持久化框架。
服務(Services)。為上層建筑提供可操作的接口,負責對領域對象進行調度和封裝,同時可以對外提供各種形式的服務。
聚合(Aggregate)。主要將復雜領域中關系密切的多個實體的合并在一起,以降低領域的復雜性。聚合內實體可以相互引用,兩個聚合之間的實體必須通過聚合根才能引用。
領域驅動設計的專注點在于領域模型的研究,因為在領域驅動設計中,它是以模型驅動設計為根基,以軟件領域為著眼點,專注于領域模型的構建與代碼匹配,并將模型作為領域專家和軟件開發人員交流的一種開發方式[3]。相對于以往的數據庫驅動的設計方式,這種新開發方式具有以下優勢:
(1)復用。在領域驅動設計中,領域對象是核心,每個領域對象都是一個相對完整的內聚的業務對象描述,所以可以形成直接的復用。同時設計過程是基于領域對象而不是基于數據庫的Schema,所以整個設計也是可以復用。
(2)注重實踐。專注于具體場景的應用,領域專家、開發人員及用戶使用模型元素之間的交互來理清系統中的業務邏輯,且按模型允許方式將各種概念結合在一起,然后將這些應用到圖和代碼中,消除了開發中思想的隔膜,保證領域模型與系統業務相對應。
(3)重構。領域驅動設計采用面對對象的設計,使領域模型在設計中隨時響應用戶提出新的軟件需求,根據業務邏輯向更深層次重構。
這里以一個業務邏輯稍為簡單的網上書店的電子商務平臺來闡述領域驅動設計在Web平臺中應用情況。該系統實現網上書店的常用功能:包括瀏覽書籍、挑選書籍、提交訂單、查看訂單、自動折扣、處理訂單、取消訂單等。未登錄用戶可以瀏覽和挑選書籍;已登錄用戶可以提交和查看自己相關的訂單;管理員可以處理訂單。結合書店的業務場景,抽象出以下一些領域對象,如訂單、賬戶、書籍、購物車、購物項、折扣等,現實業務和領域對象的對應關系為:訂單—Order,賬戶—Account,書籍—Book,購物車—Cart,購物項—Item,折扣—Discount。通過對場景及業務邏輯的分析和設計,得到的領域模型如圖2所示。

圖2 網上書店業務邏輯圖
在圖2中,首先BookStoreAction負責處理表示層的請求,并把請求轉發給業務服務IBookStoreBS,業務服務負責調度上圖中顯示的領域對象,處理該場景的所有業務。從圖中我們還可以清晰地看到各個領域對象之間的關系。Order和Cart都聚合了Item,都是聚合根,對應都是1…n,Item聚合了Book,Item是一個聚合根,Book是一個實體,對應關系1…n,Order分別與折扣、賬戶發生關聯和調用等,整個網上書店的場景就這樣描述出來了。
與事務腳本的編程模式不同,領域驅動設計不是把業務邏輯放在業務服務(Business Service)層中,而是由具備屬性、行為和狀態的領域對象處理。例如Order類,如果是貧血的POJO,那它內部只有與數據表字段對應的屬性以及getter和setter方法,而在領域驅動設計中,則是一個相對獨立的、能夠處理自身關聯業務的領域對象。如在本系統中,訂單類中除了聯系方式、郵寄地址等基本屬性外,還有以下領域相關的行為:
(1)init(·),結算時調用方法,根據當前用戶與購物車中的Items初始化訂單,供用戶修改。
(2)submit(·),提交訂單時調用的方法,保存訂單。
(3)cancel(·),取消訂單,把訂單和相關item的狀態設置為“已取消”,然后委托基礎結構層進行持久化。
(4)dispose(·),處理訂單,首先更新訂單項的狀態,然后委托基礎結構層持久化訂單數據。
……
通過以上的描述,我們可以看到,Order類基本上覆蓋了現實世界中訂單這個業務的所有行為和狀態,是相對內聚的,這樣的特性使其復用性大大增加,即使未來開發新的模塊,涉及到訂單業務的,可以直接復用Order類,同時在后期維護中,如果想了解訂單的業務,直接讀Order的代碼就可以了。
在項目開發中,良好的框架設計可以有效地提高工作效率,縮短開發時間、降低開發成本,增強程序的可維護性和可擴展性。根據領域模型的特點,在具體的項目開發中,運用分層架構和.NET提供的實體框架[4],對一些相關的類和框架進行抽象設計,設計了一些通用模塊,如層超類、接口、倉儲框架、倉儲工廠、倉儲基類和工作單元等,為將來構建一個大型的網上書店的設計與實現打下基礎,而且,項目的一些通用模塊與具體的實現細節無關,即可以直接應用到其他系統的設計中,提高開發效率。
當某一層中所有的對象都具有某些方法,同時為了避免這些方法在系統內被多次復制而產生冗余時,便將這些行為移到一個通用的類中,這個類就是層超類型(Layer Supertype),然后其他接口都被重構[6]到這一超類中。在網上書店的系統框架中,設計一個EntityBase的抽象類作為基礎結構層的層超類型,領域模型中的所有實體類都繼承它的標識Key。
層超類的分別包含一個缺省的構造器和一個重載構造器,重載構造器允許傳入一個只讀屬性的Key,考慮到不同的實體標識類型不一致,所以這里的Key的類型是System.Object,體現了超類設計的靈活性。
倉儲是領域層與基礎結構層的一個銜接組件,領域層通過倉儲訪問外部存儲機制,這樣就使得領域層無需關心任何技術架構上的實現細節。因此,倉儲這個角色的職責不僅僅是讀取、保存、查詢、刪除,它還解耦了領域層與基礎結構層,將繁雜的數據庫操作從領域層中解放出來,使開發人員更好地專注于領域層的設計。在實踐中,可以使用依賴注入[7]的方式,將倉儲實例注入到領域層,從而獲得靈活的體系結構,如圖3所示。
在本系統中,IRepository(倉儲接口)是一個泛型接 口[8],泛 型 類 型 被 where 子 句 限 定 為EntityFramework中的EntityBase,該接口使用泛型,方便被其他特定的聚合類型的倉儲繼承后使用,定義方式見如下代碼所示:


為了消除大量重復的代碼,倉儲框架將在倉儲接口的基礎上添加了抽象的倉儲基類RepositoryBase,該基類實現了IRepository<T>,從而方便系統中具體倉儲類的重構。SqlRepositoryBase<T>基類實現了RepositoryBase<T>類,是專門針對SQL Server數據庫讀取和寫入數據的通用的基類,它可以減少大量具體倉儲中的重復代碼。具體如圖4所示。

圖3 基礎結構層將倉儲實現注入領域層

圖4 倉儲框架的構建模式
在具體業務的實現中,倉儲作為領域模型的一部分,領域模型依賴于倉儲的抽象,倉儲提供一套方法將聚合從持久層中提取出來,包括一系列的查詢,保存根的方法,以及從根開始訪問聚合內其他領域模型的方法。如在Order(訂單)聚合根中,具體實現倉儲為Order,該倉儲繼承了 IOrder接口和 SqlRepositoryBase抽象類,SqlRepositoryBase繼承了倉儲框架中的RepositoryBase類。為了方便倉儲類重構,系統在初始的框架RepositoryBase基類中添加了一些常用的方法如 Add、Edit、Delete、FindBy、FindAll 等,然 在SqlRepositoryBase基類中覆蓋該方法,于是在具體的倉儲實現類Order中便可以直接調用這些方法而不需要再次實現,當然,如果實現的功能稍有差導,也可以重寫該方法。同理,其他實現類如Account(賬戶),Book(書籍),Cart(購物車)都可以像Order類那樣創建倉儲并繼承倉儲基類和層超類,這樣整個系統便提高了代碼利用率,同時也方便系統向更深層次模型重構,以獲取更合理的實現方式,確保系統在開發中有更好的可維護性和可擴展性。
工廠模式[9](Factory)實際就是在處理復雜的對象創建時,將對象創建的職責交給第三方來完成,從而降低對象創建的復雜度,增加系統的可靠性。這里本系統使用分離接口模式(Separated Interface)創建倉儲工廠實例。在基礎結構層中共設計了四個配置類來創建倉儲的映射。通過讀取配置節設置,并復制到一個良好的對象模型中,倉儲工廠類便可以利用這個對象模型創建倉儲。工廠類首先使用反射[10]來取得接口類型的名稱,然后查找基于映射配置中的將要創建的倉儲類型,如沒有,則使用Activator對象的反射能力創建一個正確的倉儲工廠實例,并將其放到靜態詞典中以供下次檢索,從而避免系統頻繁構建倉儲,節省系統開銷。
Unit of Work(UoW)模式在企業應用架構中被廣泛使用,它能夠將領域模型中對象狀態的變化收集起來,并在適當的時候在同一數據庫連接和事務處理上下文中一次性將對象的變更提交到數據庫中,減少與數據庫交互次數,提高系統性能[11]。在系統基礎結構層中設計了一個工作單元接口IUoW,用于標識已經添加、更改或移除的實體,UoW類實現IUoW接口,UoW類分別遍歷所有刪除、添加和更改的注冊信息,整個操作被包裝進一個事務,僅調用接口的Commit方法就可以將所有的更改提交到數據庫中,保證了數據的一致性。UoW類引用了IUnitOfWorkRespository接口,這個接口被倉儲中的RepositoryBase基類實現,于是工作單元的實現便回調到倉儲里實現。
應用服務層位于分層架構中的第二層,它不負責處理任何業務邏輯,主要是協調其他層的數據傳輸、工廠調用或對象發送等,為業務邏輯的正確執行提供適當的運行環境。在應用服務層,采用了微軟的窗口通信基礎[12](Windows Communication Foundation,WCF)技術為系統的上下層提供數據通信。WCF使用SOAP通信機制[13],保證了系統之間的互操作性,即使是運行不同開發語言,也可以跨進程、跨機器甚至于跨平臺的通信,同時可以提供高效且安全性的訪問。WCF在數據傳輸中使用數據合約(Data Contract)來訂定雙方溝通時的數據格式,如涉及到訂單處理的部分,應用層僅僅是協調倉儲操作和事務處理,業務邏輯由Order的倉儲方法實現。對于部分不屬于單獨的對象、不能輕易地合并到某個實體和值對象的操作或者涉及到幾個場景實體的業務邏輯,這種行為一般放在服務層,聲明為服務,設計好后供表示層直接調用,簡化了對領域模型的設計,使領域模型更純凈。如在管理員處理訂單這個場景中,首先需要根據訂單信息獲取賬戶,根據賬戶信息確定折扣率,同時進行余額校驗,如果校驗通過,就會調用訂單對象的dispose方法處理訂單,這個場景會涉及到 Order、Account、Discount等對象,這樣的業務邏輯,則聲明為一種服務,在應用層實現。
對于領域驅動設計,最核心的就是如何解決復雜業務的設計問題,如何抓住業務邏輯的本質,并轉換成業務邏輯模型。領域驅動設計良好的支撐框架和富有彈性的需求分析過程,已經得到許多企業的認可,并且它不依附于哪一個特定的平臺,這就為廣大開發者提供更有彈性的開發空間,更有利領域驅動設計的思想融入各個領域Web平臺中,加快這方面的研究和應用。目前已經有許多開發人員嘗試著應用到石油、航海[14]、物流[15]及信息系統等項目中,隨著軟件市場的進一步成熟和客戶需求的不斷提高,相信這是一個切實可行且具有很好應用前景的開發方法。
[1] 宋 波,趙永翼,張 悅,等.一種規范Web開發框架的研究與實現[J].微電子學與計算機,2007(7):201-208.
[2] Eric Evan.領域驅動設計-軟件核心復雜性應對之道[M].陳大峰,張澤鑫譯.北京:清華大學出版社,2006.
[3] 嚴欣品.領域驅動設計方法的研究及其應用[D].南昌:南昌大學,2010.
[4] 雷 蕾,陸新泉,李 睿,等.應用_NET框架命名空間技術實現Web測試自動化[J].計算機應用研究,2010,27(6):.
[5] Tim Macarthy.領域驅動設計C#2008實現[M].UMLChina譯.北京:清華大學出版社,2010.
[6] 科瑞夫斯蓋.重構與模式[M].楊 光,劉基誠譯.北京:人民郵電出版社,2006.
[7] 張 浩.利用反向控制原則和依賴注入的可復用框架設計解耦方法[J].計算機應用,2010,30(12):227-229.
[8] 陳葉旺,余金山.泛型編程與設計模式[J].計算機科學,2006,33(4):253-257.
[9] 彭世康,周逢權.新的設計模式——數組工廠和數組原型模式[J].計算機應用,2012,32(S2):107-112.
[10] 吳東慶,胡小健,楊逢建.反射機制下類工廠模式的實現與研究[J].計算機應用,2006,26(3):705-707.
[11] Martin Fowler.企業應用架構模式[M].王懷民,周斌譯.北京:機械工作出版社,2004.
[12] 劉黎志,吳云韜.應用WCF分布式框架實現移動數據同步[J].計算機應用,2011,12(31):3281-3284.
[13] 劉嘉?。赟OA架構的ERP與電子商務系統研究[J].企業經濟,2011(5):88-90.
[14] 張金松.領域驅動設計在航務海事系統中的應用研究[D].大連:大連海事大學,2010.
[15] 丁 濤.基于領域驅動設計的物流平臺系統實現[D].成都:電子科技大學,2010.