吳昌雨,王善勤,李云松,劉青,鄒軍國(guó)
(滁州職業(yè)技術(shù)學(xué)院,安徽滁州239000)
傳統(tǒng)的軟件開發(fā)經(jīng)常是分析與設(shè)計(jì)割裂的,一個(gè)典型的例子就是在我國(guó)系統(tǒng)分析師、系統(tǒng)設(shè)計(jì)師就是兩種不同的職稱,分析與設(shè)計(jì)分離導(dǎo)致的后果就是分析的結(jié)果往往不能直接用于設(shè)計(jì)編程,設(shè)計(jì)者需要從分析文檔中給出數(shù)據(jù)設(shè)計(jì)逆推出系統(tǒng)的行為,最終造成設(shè)計(jì)出的軟件并不能真正的體現(xiàn)需求,而領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain-Driven Design,DDD)是Eric Evans在其著作《Domain-Driven Design–Tackling Complexity in the Heart of Software》[1]中首次提出的一種用于指導(dǎo)復(fù)雜軟件設(shè)計(jì)與開發(fā)的一整套基于領(lǐng)域模型的系統(tǒng)分析和設(shè)計(jì)的方法.它將軟件分析與設(shè)計(jì)的關(guān)注點(diǎn)從數(shù)據(jù)引導(dǎo)到業(yè)務(wù)上來,打破了分析與設(shè)計(jì)的隔閡,提出了領(lǐng)域模型概念,使得軟件能夠適應(yīng)更靈活的需求變更.
本文從教學(xué)資源共享平臺(tái)的分析與設(shè)計(jì)入手,闡述了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的相關(guān)理論及其應(yīng)用,通過應(yīng)用六邊形架構(gòu)實(shí)現(xiàn)了系統(tǒng)原型,為類似軟件開發(fā)過程中領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)方法的應(yīng)用提供借鑒.
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)是面向?qū)ο蟮姆治雠c設(shè)計(jì)(Object Oriented Analysis Design,OOAD)的擴(kuò)展和延伸,它既是面向?qū)ο笤O(shè)計(jì)的補(bǔ)充,又完成了對(duì)面向?qū)ο笤O(shè)計(jì)的超越,相對(duì)OOAD而言,它的主要變化在于能夠使用領(lǐng)域模型準(zhǔn)確反應(yīng)業(yè)務(wù)語言,也正因?yàn)榇耍鼛缀醭闪四壳伴_發(fā)大中型復(fù)雜軟件系統(tǒng)的主流方法.國(guó)內(nèi)外研究學(xué)者對(duì)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的核心構(gòu)成要素如分層架構(gòu)、實(shí)體、值對(duì)象服務(wù)等概念展開了大量研究.例如Vaughn Vernon的《Implementing Domain-Driven Design》(實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))從戰(zhàn)略設(shè)計(jì)高度研究了包括領(lǐng)域、實(shí)體、值對(duì)象、受限上下文等概念如何設(shè)計(jì),并從戰(zhàn)術(shù)設(shè)計(jì)的角度研究了其如何實(shí)施[2];Mat Wall等人從Guardian.co.uk網(wǎng)站,采用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)后其架構(gòu)演進(jìn)的角度著手分析了如何在既有項(xiàng)目上應(yīng)用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)并不斷演進(jìn)[3];Jimmy Nilsson在其著作《Applying domain-driven design and patterns》(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)與模式實(shí)戰(zhàn))中,展示了如何應(yīng)用測(cè)試驅(qū)動(dòng)開發(fā)(TDD)不斷改進(jìn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)以及應(yīng)用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)創(chuàng)建高質(zhì)量企業(yè)級(jí)應(yīng)用架構(gòu)的過程[4].
和傳統(tǒng)軟件開發(fā)一樣,軟件開發(fā)首先是從軟件專家與項(xiàng)目領(lǐng)域?qū)<业慕涣鏖_始,但這種交流通常會(huì)存在障礙,原因是雙方思維方式及問題的側(cè)重點(diǎn)是不一致的,所以領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的一個(gè)核心的原則就是使用一種基于模型的通用語言(Ubiquitous Language)實(shí)現(xiàn)相互的交流.圖1展示了通用語言是介于開發(fā)者與領(lǐng)域?qū)<医M成的開發(fā)團(tuán)隊(duì)所使用的用于統(tǒng)一其行動(dòng)及幫助創(chuàng)建統(tǒng)一模型的語言.
圖1 構(gòu)建通用語言
以教學(xué)資源共享平臺(tái)開發(fā)為例,首先由領(lǐng)域?qū)<覍?duì)其需求進(jìn)行定義:教師可以創(chuàng)建并管理課程;課程由章節(jié)構(gòu)成,每個(gè)章節(jié)包括一定學(xué)時(shí)的教學(xué)內(nèi)容;章節(jié)中的教學(xué)內(nèi)容由文檔、視頻、音頻等教學(xué)資源構(gòu)成;學(xué)生可以瀏覽并收藏課程內(nèi)容.
如果是使用傳統(tǒng)的面向?qū)ο蟪绦蛟O(shè)計(jì)方法,根據(jù)其需求可以由動(dòng)名詞法得到一些實(shí)體類,類之間包含一些屬性及其get/set方法,這些實(shí)體類的作用很單一,僅僅用于描述實(shí)體卻沒有任何與其業(yè)務(wù)邏輯相關(guān)的東西,業(yè)務(wù)邏輯將會(huì)被放到一個(gè)單獨(dú)的service類中處理,這是一種典型的失血模型.而采用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)方法則要求將業(yè)務(wù)邏輯集中于領(lǐng)域?qū)ο笾?,同樣是上面的例子,課程領(lǐng)域模型可以被視為一個(gè)聚合,課程作為聚合根包含了章節(jié)與課程資源,聚合根內(nèi)部包含有狀態(tài),并且這種狀態(tài)不能直接暴露出去,另外聚合內(nèi)部的對(duì)象通過聚合根實(shí)體與外界交互.因此從邏輯上得出這樣一個(gè)結(jié)論:教師、學(xué)生作為用戶與課程發(fā)生業(yè)務(wù)邏輯,而課程作為聚合根其內(nèi)部包含了章節(jié)及教學(xué)資源,即形成了圖2所示的領(lǐng)域模型.
圖2 教學(xué)資源共享平臺(tái)領(lǐng)域模型
這種領(lǐng)域模型準(zhǔn)確的反應(yīng)了業(yè)務(wù),業(yè)務(wù)邏輯不是放在單獨(dú)的業(yè)務(wù)邏輯類中處理,而是包含在領(lǐng)域?qū)ο笾?,每個(gè)領(lǐng)域?qū)ο蠖际前藢傩耘c業(yè)務(wù)邏輯相對(duì)完整的獨(dú)立體,并與現(xiàn)實(shí)領(lǐng)域中的業(yè)務(wù)對(duì)象一一映射.領(lǐng)域模型則是由這些領(lǐng)域?qū)ο蠼M成.這種設(shè)計(jì)方法,即保證了系統(tǒng)的可維護(hù)性、擴(kuò)展性和復(fù)用性,同時(shí)也在處理復(fù)雜業(yè)務(wù)邏輯方面具有先天優(yōu)勢(shì).
Eric Evans在《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》中給出了一個(gè)典型的四層參考架構(gòu),分別是用于展示信息,并解釋用戶命令的表現(xiàn)層;起到協(xié)調(diào)、調(diào)度作用的應(yīng)用層;核心的領(lǐng)域?qū)?,包括業(yè)務(wù)領(lǐng)域的信息,以及業(yè)務(wù)對(duì)象的狀態(tài)變更;提供業(yè)務(wù)對(duì)象的持久化等支撐的基礎(chǔ)設(shè)施層.
這種分層架構(gòu)很好的遵循了關(guān)注點(diǎn)分離的原則,對(duì)領(lǐng)域?qū)ο筮M(jìn)行了明確的策略和職責(zé)劃分,讓領(lǐng)域?qū)ο蠛同F(xiàn)實(shí)世界中的業(yè)務(wù)形成良好的映射關(guān)系,相比于傳統(tǒng)的軟件架構(gòu)分層有如下特點(diǎn):
1)應(yīng)用層不包含業(yè)務(wù)邏輯,由領(lǐng)域?qū)犹幚砭唧w的業(yè)務(wù)操作
傳統(tǒng)三層架構(gòu)軟件設(shè)計(jì)中,有專門用于處理業(yè)務(wù)邏輯的業(yè)務(wù)邏輯層(BLL),在這樣的架構(gòu)中,隨著需求的變更業(yè)務(wù)邏輯處理類開始積聚越來越多的業(yè)務(wù)邏輯,而領(lǐng)域?qū)ο髣t成為單純的數(shù)據(jù)載體造成了“肥的服務(wù)層”和“貧血的領(lǐng)域模型”.而在DDD方法指導(dǎo)下,領(lǐng)域模型應(yīng)該側(cè)重于具體的業(yè)務(wù)操作領(lǐng)域.領(lǐng)域?qū)ο笥蓪?shí)體和值對(duì)象構(gòu)成,實(shí)體類具備自己的屬性和行為、狀態(tài),可以聚合,實(shí)體類之間可以有聚合關(guān)聯(lián)等關(guān)系,可以借由基礎(chǔ)設(shè)施層進(jìn)行持久化.
2)領(lǐng)域?qū)硬灰蕾囉趯?shí)現(xiàn)的細(xì)節(jié),層與層之間松耦合
在軟件分層結(jié)構(gòu)中,層通常是職責(zé)劃分為獨(dú)立且緊密結(jié)合的單元,比如傳統(tǒng)三層架構(gòu)中BLL層負(fù)責(zé)業(yè)務(wù)邏輯,它依賴于底層數(shù)據(jù)訪問層的支持同時(shí)也為其上級(jí)表示層提供依賴,這種層與層之間的依賴關(guān)系看起來很自然,但在具體面對(duì)需求變化時(shí),每一個(gè)層次的變更都有可能影響到其他層,并對(duì)系統(tǒng)的伸縮性產(chǎn)生負(fù)面影響.而在DDD中,領(lǐng)域?qū)与m然負(fù)責(zé)處理整個(gè)系統(tǒng)的業(yè)務(wù)邏輯,但其設(shè)計(jì)是與其他層松耦合,即與其上下層之間沒有依賴關(guān)系,領(lǐng)域模型業(yè)務(wù)邏輯的實(shí)現(xiàn)應(yīng)該獨(dú)立于持久化實(shí)現(xiàn)的細(xì)節(jié).
事實(shí)上,DDD的具體實(shí)現(xiàn)并不依賴于特定架構(gòu),包括其參考架構(gòu)的層次概念在實(shí)踐中都是可以忽略的,本文針對(duì)教學(xué)資源共享平臺(tái)采用了圖3所示的六邊形架構(gòu)(Hexagonal architecture),圖中左邊是六邊形架構(gòu),右邊是資源共享平臺(tái)實(shí)現(xiàn)過程中針對(duì)六邊形架構(gòu)的一些具體實(shí)現(xiàn).
圖3 六邊形架構(gòu)
這種六邊形架構(gòu)也可以稱之為端口和適配器架構(gòu)(Ports and Adapters architecture)[5],該架構(gòu)設(shè)計(jì)目標(biāo)是實(shí)現(xiàn)層次之間的解耦,在其核心的領(lǐng)域模型中包含了所有的業(yè)務(wù)邏輯與規(guī)則(但并不直接實(shí)現(xiàn),由基礎(chǔ)設(shè)施層通過DI注入);包圍領(lǐng)域模型的是應(yīng)用程序端口層,它負(fù)責(zé)接收請(qǐng)求,并交由領(lǐng)域?qū)犹幚?,這一層很薄,主要起到協(xié)調(diào)作用;最外層的是適配器層,負(fù)責(zé)以某種格式接受輸入并產(chǎn)生輸出,比如通過HTTP接受客戶端請(qǐng)求并封裝為端口能夠理解的方式交給端口,再將處理結(jié)果轉(zhuǎn)換為HTTP相應(yīng)反饋給客戶端.該架構(gòu)的特點(diǎn)是組件與組件之間是相互平等的,模糊了層次概念,因?yàn)楦鲗哟沃g的交互并不依賴于各自于實(shí)現(xiàn)的細(xì)節(jié),都是通過接口實(shí)現(xiàn),這一特性的實(shí)現(xiàn)取決與軟件抽象及一些新技術(shù)手段的運(yùn)用.具體來說,該架構(gòu)的實(shí)現(xiàn)需要以下三種技術(shù)手段的配合:
1)OOP(Object Oriented Programming,面向?qū)ο缶幊?,OOP仍然是領(lǐng)域?qū)崿F(xiàn)中的重要原則,應(yīng)充分利用其封裝、繼承、接口等特性設(shè)計(jì)領(lǐng)域?qū)ο?
2)DI((Dependency Injection,依賴注入),DDD要求領(lǐng)域?qū)ο蠹纫哂袠I(yè)務(wù)邏輯但又不能依賴于具體實(shí)現(xiàn)細(xì)節(jié),則只能通過DI的方式將數(shù)據(jù)持久化等業(yè)務(wù)邏輯注入到領(lǐng)域?qū)ο笾?
3)AOP(Aspect Oriented Programming,面向方面編程),AOP可以很好的實(shí)現(xiàn)關(guān)注點(diǎn)橫切,比如可以使用AOP將領(lǐng)域?qū)ο蟮臉I(yè)務(wù)規(guī)則檢查、狀態(tài)變化跟蹤、數(shù)據(jù)緩存、事務(wù)管理等某個(gè)方面的問題從領(lǐng)域?qū)ο笾幸瞥鰜?,讓領(lǐng)域?qū)ο蟾玫年P(guān)注業(yè)務(wù).
領(lǐng)域?qū)ο笥蓪?shí)體及值對(duì)象構(gòu)成,實(shí)體(Entities)類具有唯一的ID,能夠?qū)崿F(xiàn)持久化等業(yè)務(wù)邏輯,對(duì)應(yīng)于現(xiàn)實(shí)世界中的業(yè)務(wù)對(duì)象,在系統(tǒng)中設(shè)計(jì)了Course等實(shí)體類;值對(duì)象無ID,由對(duì)象屬性描述,可用于傳遞數(shù)據(jù)或?qū)?shí)體進(jìn)行補(bǔ)充描述.
基于領(lǐng)域模型,教學(xué)資源共享平臺(tái)的通過設(shè)計(jì)與分析教學(xué)資源共享平臺(tái)領(lǐng)域?qū)釉O(shè)計(jì)圖如圖4所示:
圖4 教學(xué)資源共享平臺(tái)領(lǐng)域建模
圖中領(lǐng)域?qū)ο蠛同F(xiàn)實(shí)業(yè)務(wù)的對(duì)應(yīng)關(guān)系為:Teacher——教 師、Student——學(xué) 生、Course——課程、Lesson——教學(xué)章節(jié)、Resource——教學(xué)資源.這5個(gè)領(lǐng)域?qū)ο蟀凑展δ軇澐譃閮蓚€(gè)模塊,分別是用戶模塊和課程模塊,將這些高關(guān)聯(lián)度的類劃分到一個(gè)模塊,可以提供盡可能大的內(nèi)聚性,從圖3中可以看出每個(gè)模塊通過一個(gè)定義好的接口被其他的模塊訪問,比如用戶模塊通過IUserService接口的實(shí)現(xiàn)類UserService服務(wù)與外部交互,其目的是降低系統(tǒng)耦合度.UserService以及CourseService都屬于DDD中的領(lǐng)域服務(wù),它為外部提供操作接口,負(fù)責(zé)對(duì)領(lǐng)域?qū)ο筮M(jìn)行調(diào)度與封裝并提供各種形式的服務(wù),服務(wù)執(zhí)行的操作代表了一個(gè)領(lǐng)域概念,被執(zhí)行的操作通常會(huì)涉及到領(lǐng)域中的其他對(duì)象,以刪除課程為例,該業(yè)務(wù)邏輯不僅僅需要?jiǎng)h除課程還需考慮如何處理與其相關(guān)聯(lián)的章節(jié)及教學(xué)資源,此時(shí)將業(yè)務(wù)邏輯放到服務(wù)中是一種更合理的做法.
在領(lǐng)域?qū)ο笤O(shè)計(jì)過程中還應(yīng)該處理好對(duì)象之間的關(guān)系,通常領(lǐng)域?qū)ο笾g會(huì)相互產(chǎn)生各種聯(lián)系,甚至形成一個(gè)復(fù)雜的關(guān)系網(wǎng),比如在教學(xué)資源共享平臺(tái)中一門課程擁有多個(gè)教學(xué)章節(jié),這是一個(gè)典型的一對(duì)多關(guān)系;一位老師可以創(chuàng)建多門課程應(yīng)該也是一個(gè)一對(duì)多關(guān)系,但同樣的一對(duì)多關(guān)系在設(shè)計(jì)過程中還應(yīng)區(qū)別對(duì)待,對(duì)DDD中的領(lǐng)域模型而言,其設(shè)計(jì)目標(biāo)并非讓其具備完整的關(guān)聯(lián)關(guān)系,而是盡量的簡(jiǎn)化關(guān)系.復(fù)雜的關(guān)聯(lián)關(guān)系只會(huì)讓管理對(duì)象生命周期變得困難,簡(jiǎn)化關(guān)系可以采取刪除非基本關(guān)聯(lián)關(guān)系、添加約束減少多重性、雙向關(guān)聯(lián)轉(zhuǎn)為單項(xiàng)關(guān)聯(lián)等手段實(shí)現(xiàn).教學(xué)資源共享平臺(tái)的開發(fā)采用了Groovy語言,以Course領(lǐng)域?qū)ο笤O(shè)計(jì)為例,其代碼如下:
代碼中展示了Course與Lesson之間的一對(duì)多關(guān)系,因?yàn)镃ourse與Lesson之間業(yè)務(wù)上緊密相連,其關(guān)系應(yīng)在模型中體現(xiàn).但在Course與Teacher之間的關(guān)系的處理上,考慮到邏輯上他們分屬兩個(gè)不同的模塊,Course領(lǐng)域?qū)ο笾芯S護(hù)其關(guān)系將導(dǎo)致額外的復(fù)雜性,因而并未在Course領(lǐng)域?qū)ο笾兄苯芋w現(xiàn)兩者之間的關(guān)系,而是通過teacherID維護(hù)其關(guān)聯(lián)關(guān)系,另外在代碼中通過設(shè)置約束來維護(hù)其關(guān)聯(lián)關(guān)系的完整性,比如定義一個(gè)約束用來保證只有課程的創(chuàng)建者才可以執(zhí)行課程的維護(hù),代碼如下:
def isCourseOwner(Teacher teacherInstance,Course courseInstance){
return courseInstance.teacher.id.equals(teacher-Instance.id)
}
在進(jìn)行刪除、修改等操作之前需要先調(diào)用該方法確認(rèn)當(dāng)前操作者與課程中TeacherID一致才可以繼續(xù)進(jìn)行.
包圍領(lǐng)域模型的是應(yīng)用程序端口層它負(fù)責(zé)接收請(qǐng)求,并交由領(lǐng)域?qū)犹幚?,這一層很薄,主要起到協(xié)調(diào)作用.
適配器層是最外部的一層,它包含了與各種外部設(shè)備對(duì)接的適配器,比如針對(duì)Web瀏覽器用戶的適配器、針對(duì)數(shù)據(jù)庫(kù)交互的適配器、針對(duì)外部服務(wù)的適配器、甚至包括針對(duì)自身內(nèi)部操作的適配器等,這些適配器有些需要自行開發(fā)也有些可以利用基礎(chǔ)實(shí)施層的一些中間件來實(shí)現(xiàn)其功能.
教學(xué)資源共享平臺(tái)的基礎(chǔ)設(shè)施層主要利用了一些JavaEE開源組件來構(gòu)建,其中包括Hibernate實(shí)現(xiàn)數(shù)據(jù)持久化;Spring MVC框架實(shí)現(xiàn)IOC及AOP;JDBC實(shí)現(xiàn)數(shù)據(jù)庫(kù)驅(qū)動(dòng)等.
采用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的最大優(yōu)勢(shì)是直接將核心業(yè)務(wù)邏輯與領(lǐng)域模型結(jié)合起來,而不用向傳統(tǒng)軟件設(shè)計(jì)那樣分割為數(shù)據(jù)與行為,這種優(yōu)勢(shì)使其在復(fù)雜軟件設(shè)計(jì)中已成為主流思想,基于其設(shè)計(jì)教學(xué)資源共享平臺(tái)充分應(yīng)用了領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)方法的相關(guān)理論,在其四層參考架構(gòu)的基礎(chǔ)上研究了基于六邊形架構(gòu)的具體實(shí)現(xiàn),模糊了分層概念,更為充分的體現(xiàn)了軟件設(shè)計(jì)中高內(nèi)聚、低耦合的要求.
[1]Eric Evans.Domain-Driven Design[M].Boston:Addison-Wesley Professional,2003.
[2]Vaughn Vernon.Implementing Domain-Driven Design[M].Boston:Addison-Wesley Professional,2013.
[3]Mat Wall,Nik Silver.演進(jìn)架構(gòu)中的領(lǐng)域驅(qū)動(dòng)設(shè)計(jì).[EB/OL].王麗娟譯.http://www.infoq.com/cn/articles/ddd-evolving-architecture.2009.
[4]Jimmy Nilsson.領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)與模式實(shí)戰(zhàn)[M].趙俐,馬燕新,譯.北京:人民郵電出版社,2009.
[5]Alistair Cockburn.Hexagonal architecture.[EB/OL].http://alistair.cockburn.us/Hexagonal+architecture.2010.