領域驅動設計與模型驅動開發(fā)_第1頁
領域驅動設計與模型驅動開發(fā)_第2頁
領域驅動設計與模型驅動開發(fā)_第3頁
領域驅動設計與模型驅動開發(fā)_第4頁
領域驅動設計與模型驅動開發(fā)_第5頁
已閱讀5頁,還剩137頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

領域驅動設計

與模型驅動開發(fā)鐘瑋軍2015年3月當前第1頁\共有142頁\編于星期三\12點致謝:

此培訓材料借鑒了來自參考文獻以及互聯網的大量資料,部分資料的參考來源未能盡數列舉,謹在此對那些在網絡中無私分享自己知識的人表達我的衷心感謝!當前第2頁\共有142頁\編于星期三\12點培訓內容領域驅動設計簡介領域通用語言領域驅動設計的構造塊領域驅動設計編程實踐CQRS架構模型驅動開發(fā)當前第3頁\共有142頁\編于星期三\12點領域驅動設計思想的發(fā)展2002年MartinFower在其出版《企業(yè)應用架構模式》中,歸納總結了40多種企業(yè)應用架構的設計模式。其中所提到的多種設計模式和概念,如事務腳本、活動記錄和領域模型等,對業(yè)界產生了深遠的影響。2004年著名建模專家EricEvans發(fā)表了他最具影響力的著名書籍:《Domain-DrivenDesign–TacklingComplexityintheHeartofSoftware》(中文譯名:領域驅動設計—軟件核心復雜性應對之道),書中提出了“領域驅動設計(簡稱DDD)”的概念。2010年GregYoung在“CQRS,TaskBasedUIs,EventSourcingagh!

”一文中對BetrandMeyer的CQS模式進行改造,提出CQRS模式。此后JimmyNilsson的《ApplyingDomain-DrivenDesignandPatterns》、AbelAvram和FloydMarinescu合作的《Domain-DrivenDesignQuickly》、DanHaywood的《Domain-DrivenDesignUsingNakedObjects》、以及VaughnVernon的《ImplementingDomain-DrivenDesign》等書籍的出版,豐富了領域驅動設計的實踐和指導。當前第4頁\共有142頁\編于星期三\12點領域驅動設計是什么領域驅動設計事實上針對是OOAD的一個擴展和延伸,DDD基于面向對象分析與設計技術,對技術框架進行了分層規(guī)劃,同時對每個類進行了策略和類型的劃分。It’sasetofprovenmodelingtechniquesespeciallytargetedtocomplexapplications.It’sasetofprinciplesandpracticessupportingthedevelopmentprocess.It’sasetofpatternsthatsupportacleanandcoherentviewofthedomainmodel.It’sasetofpragmaticstrategiesallowingapplicationstoscaleinsizeandcomplexitymaintainingtheirintegrity.當前第5頁\共有142頁\編于星期三\12點領域驅動設計的特性分層架構成熟、清晰的分層架構領域對象與現實世界的業(yè)務映射明確的職責劃分復用領域對象是核心領域對象復用:完整的業(yè)務對象描述設計復用:設計基于領域對象而非數據庫使用場景具備復雜業(yè)務邏輯的軟件開發(fā)對設計和開發(fā)人員要求較高不適用普通CRUD的業(yè)務軟件的維護性和擴展性良好(Testable)當前第6頁\共有142頁\編于星期三\12點領域驅動設計分層規(guī)劃(一)領域驅動設計分層規(guī)劃用戶界面/展現層負責向用戶展現信息以及解釋用戶命令。展示層的組件實現用戶與應用交互的功能。一般建議用MVC,MVP或者MVVM模式來分隔這些組件為子層應用層很薄的一層,用來協調應用的活動,實現協調應用的“通道”,例如事務、執(zhí)行單位操作、調用應用程序的任務。它不包含業(yè)務邏輯。它不保留業(yè)務對象的狀態(tài),但它保有應用任務的進度狀態(tài)。類似于Fa?ade模式,調用領域層和基礎設施層來完成應用的用例。領域層本層包含關于領域的信息。這是業(yè)務軟件的核心所在。在這里保留業(yè)務對象的狀態(tài),對業(yè)務對象和它們狀態(tài)的持久化被委托給了基礎設施層?;A設施層本層作為其他層的支撐庫存在。它提供了層間的通信,實現對業(yè)務對象的持久化,包含對用戶界面層的支撐庫等作用。當前第7頁\共有142頁\編于星期三\12點領域驅動設計分層規(guī)劃(二)領域驅動設計是對傳統(tǒng)N層架構模式的繼承和發(fā)展當前第8頁\共有142頁\編于星期三\12點領域驅動設計分層規(guī)劃(三)領域驅動設計是對傳統(tǒng)N層架構模式的繼承和發(fā)展《CoreJ2EEPatterns》例:J2EE參考分層架構傳統(tǒng)J2EE或Spring+Hibernate等事務性編程模型只關心數據,這些數據對象除了簡單sette/getter方法外,沒有任何業(yè)務方法,被比喻成“失血模型”。當前第9頁\共有142頁\編于星期三\12點領域驅動設計分層規(guī)劃(四)分布式領域驅動設計當前第10頁\共有142頁\編于星期三\12點領域驅動設計分層規(guī)劃(五)分布式領域驅動設計與DotNET技術架構體系之間的關系映射當前第11頁\共有142頁\編于星期三\12點面向對象分析與設計技術面向過程vs.面向對象事務腳本模式把業(yè)務邏輯組織成單個過程,在過程中直接調用數據庫,業(yè)務邏輯在服務(Service)層處理。

事務腳本模式的特點是簡單容易理解,面向過程設計。對于少量邏輯的業(yè)務應用來說,事務腳本模式簡單自然,性能良好,容易理解,而且一個事務的處理不會影響其他事務。不過缺點也很明顯,對于復雜的業(yè)務邏輯處理力不從心,難以保持良好的設計,事務之間的冗余代碼不斷增多,通過復制粘貼方式進行復用??删S護性和擴展性變差。當前第12頁\共有142頁\編于星期三\12點對類的策略和類型的劃分對類進行StereoType(“構造型”)劃分的好處在于:(1)指導設計

(2)幫助命令對象

(3)輔助理解按照策略和類型對類進行劃分當前第13頁\共有142頁\編于星期三\12點六邊形架構以領域模型為核心的六邊形架構當前第14頁\共有142頁\編于星期三\12點領域驅動設計中的設計模式有助于獲得柔性設計的設計模式每個元素的名稱都提供了一次揭示設計意圖的機會。站在客戶開發(fā)人員的角度上來思考它。人們?yōu)榱耸顾蓄惡筒僮鞫季哂邢嗨频囊?guī)模而尋找一種一致的力度。粒度的大小并不是唯一要考慮的問題,我們還要考慮粒度在哪種場合下使用。隨著代碼重構不斷適合新理解的概念或需求,概念輪廓也就逐漸形成了。搞內聚低耦合原則既適用于代碼,也適用于概念?!额I域驅動設計—軟件核心復雜性應對之道》第10章任何對未來操作產生影響的系統(tǒng)狀態(tài)的改變都可以成為副作用。把命令和查詢嚴格地放到不同操作中;創(chuàng)建并返回ValueObject。允許我們安全地對多個操作進行組合。使用斷言把副作用明確表示出來,使它們更易于處理。尋找在概念上內聚的模型,更易推出預期ASSERTION,從而加快學習過程并避免代碼矛盾。盡一切可能保持低耦合。把所有無關概念提取到對象之外,類就變成完全孤立的了,使得我們可以單獨地研究和理解它。每個孤立類都極大減輕了因理解Module而帶來的負擔。操作閉合:在適當的情況下,在定義操作時讓它的返回類型與其參數相同。閉合操作提供了一個高層接口,同時又不會引入對其他概念的任何依賴性。當前第15頁\共有142頁\編于星期三\12點培訓內容領域驅動設計簡介領域通用語言領域驅動設計的構造塊領域驅動設計編程實踐CQRS架構模型驅動開發(fā)當前第16頁\共有142頁\編于星期三\12點使用通用語言的重要性Talkingdifferentlanguagesmakesprojectsfail.Programmersspeakusingtechnicaljargon(designpatterns,acronyms,geekyin-jokes)DomainexpertsuseterminologyspecifictotheirfieldofexpertiseComputersspeakprogramminglanguages大家必須妥協當前第17頁\共有142頁\編于星期三\12點領域驅動設計的關鍵點關注核心領域(CoreDomain)領域專家和軟件從業(yè)者共同開發(fā)模型在一個明確的限界上下文(BoundedContext)中使用領域通用語言(ubiquitouslanguage)當前第18頁\共有142頁\編于星期三\12點通用語言(一)通用語言(UBIQUITOUSLANGUAGE)是團隊共享的語言。領域專家和開發(fā)者使用相同的通用語言進行交流。事實上,團隊中每個人都使用相同的通用語言。不管你在團隊中的角色如何,只要你是團隊的一員,你都將使用通用語言。通用語言是團隊自己創(chuàng)建的公用語言。團隊中同時包含領域專家和軟件開發(fā)人員。通用語言更多地是關于業(yè)務本身如何思考和運作的,領域專家對通用語言有很大影響。不同領域專家會在概念和術語上產生分歧,甚至也會犯錯,當領域專家和開發(fā)者一起創(chuàng)建領域模型的時候,他們有時會達成一致,有時會做一些妥協,但最終目的都是為了創(chuàng)造最適合項目的通用語言。團隊成員們妥協的絕對不應是通用語言的質量,而是概念、術語和含義。最初的一致并不表示始終一致,通用語言也會隨著時間推移而不斷演化改變。領域驅動設計的一個核心思想就是使用基于模型的共同語言。因為模型是軟件滿足領域的共同點,它很適合作為這種通用語言的構造基礎。使用模型作為語言的核心骨架,要求團隊在進行所有的交流都是使用一致的語言,在代碼中也是這樣。在共享知識和推敲模型時,團隊會使用語言、文字和圖形。這兒需要確保團隊使用的語言在所有的交流形式中看上去都是一致的,這種語言被稱為“通用語言(UbiquitousLanguage)”。通用語言的詞匯表包括類名稱和主要操作。語言中包含術語,有些術語用來討論模型中已經明確的規(guī)則,還有一些術語則來自施加于模型上的高級組織原則。最后,團隊一致應用于領域模型的模式名稱使這種語言更為豐富。模型之間的關系成為所有語言都具有的組合規(guī)則,詞和短語的意義反映了模型的語義。當前第19頁\共有142頁\編于星期三\12點通用語言(二)在應用通用語言時,應注意:將模型作為語言的中心。確保團隊在所有交流活動和代碼中堅持使用這種語言。在畫圖、寫東西特別是講話時也要使用這種語言。通過嘗試不同的表示方法(它們反映了不同模型)來消除難點。然后重構代碼,并對類、方法和模塊重新命名,以便與新模型相一致。解決交談中的術語混淆問題,就像我們對普通詞匯形成一個公認的理解一樣。要認識到UBIQUITOUSLANGUAGE中的更改就是對模型的更改。領域專家應該避免使用拗口或無法表達領域理解的術語或結構,開發(fā)人員應該密切監(jiān)視那些將會妨礙設計的有歧義和不一致的地方有了通用語言,模型就不僅僅是一個設計工作了。它成為開發(fā)人員和領域專家共同完成的每項工作中的不可或缺的部分。語言以動態(tài)形式傳遞知識。使用這種語言進行討論能夠更清楚地表達圖和代碼背后的真實含義。通用語言是那些不以代碼形式出現的設計方面的主要載體,這些方面包括把整個系統(tǒng)組織在一起的比例結構、定義了不同系統(tǒng)和模型之間關系的BoundedContext,以及在模型和設計中使用的其他模式。當前第20頁\共有142頁\編于星期三\12點通用語言的應用通用語言貫穿于項目的各個環(huán)節(jié)UserStoriesProjectMeetingsTeamEmailsInstantMessagesSchedulePlanSoftwareDocuments在限界上下文中,保持語言的一致性(如口語、圖形(如UML圖等)、文字、代碼等)。當前第21頁\共有142頁\編于星期三\12點通用語言的應用示例(一)UserStoriesNO

WhenUserlogsonwithvalidcredentials,anemptypanelisdisplayed.YES

WhenPlayerlogsonwithvalidcredentials,anemptyboardgameisdisplayed.

(fromaTicTacToeGamesoftwareexample)當前第22頁\共有142頁\編于星期三\12點通用語言的應用示例(二)CodeExampleNO

.Integeri=newInteger();

.Stringchar1=newString();

.publicclassGameDAO(){}

.catch(Exceptione)YES

.StringrealMeaningOfMyString=newString();.publicclassScoreDataLoader(){}

.catch(ExceptionNotLoggedInException)NO

.Ambiguities

.Inconsistencies

.Synonyms

.AbbreviationsYES

.Clarity

.Precision

.Reuse

.FullNames當前第23頁\共有142頁\編于星期三\12點/***AddthestringOorXtoacellinthegrid.*/publicclassShowCellGrid{publicstaticvoiddisplayUser(Gridgrid,Cellcell){if(!Initialization.flag&&Initialization.gameStatus.getSequence()==null&&isEmpty(grid,cell)){Initialization.flag=true;Stringmk=showString(Initialization.gameStatus.getCurrentUser().getUserString());grid.setHTML(cell.getRowIndex(),cell.getCellIndex(),mk);Initialization.gameStatus.getStatus()[cell.getRowIndex()][cell.getCellIndex()]=Initialization.gameStatus.getCurrentUser();GameEnd.checkEnd(Initialization.gameStatus,cell.getRowIndex(),cell.getCellIndex());}(...)}AclassBEFOREandAFTERUbiquitousLanguage/***Performsamoveinthegame.*/publicclassPlayerMove{/***Whentheplayerclicksinacell,thegamedrawsanOoraXonthe

*gamegriddependingonwhichplayer'sturnitis.*/publicstaticvoidmakeMove

(GameGridgameGrid,Cellcell){if(!GameInitialization.waitingMoveFlag&&GameInitialization.currentGameStatus.getSequenceWinner()==null&&isCellEmpty(gameGrid,cell)){GameInitialization.waitingMoveFlag=true;Stringmarker=showPlayerIcon(GameInitialization.currentGameStatus.getCurrentPlayer().getPlayerIcon());gameGrid.setHTML(cell.getRowIndex(),cell.getCellIndex(),marker);GameInitialization.currentGameStatus.getGameMoves()[cell.getRowIndex()][cell.getCellIndex()]=GameInitialization.currentGameStatus.getCurrentPlayer();CheckWinner.checkForWinner(GameInitialization.currentGameStatus,cell.getRowIndex(),cell.getCellIndex());}(...)}(ExcerptedfromaTicTacToeGamesourcecode)當前第24頁\共有142頁\編于星期三\12點WhichonewouldaStakeholderbetterunderstand?PlayerMove

Performsamoveinthegame.

MakeMove

Whentheplayerclicksinacell,thegamedrawsanOoraXonthegamegriddependingonwhichplayer'sturnitis.IsCellEmptyThePlayercanselectacellonlyifitwasn'talreadyselected.ShowCellGrid

AddtheStringOorXtoacellinthegrid.

DisplayUser

IsEmpty(ExcerptedfromaTicTacToeGamesourcecode)當前第25頁\共有142頁\編于星期三\12點模型的統(tǒng)一模型的內部一致性又叫做“統(tǒng)一”,這樣每個術語都不會有模棱兩可的意義,也不會有規(guī)則沖突。除非模型在邏輯上是一致的,否則它就沒有意義。識別限界上下文中的不一致:重復的概念和假同源重復的概念是指兩個模型元素(以及伴隨的實現)實際上表示同一個概念。每當這個概念的信息發(fā)生改變時,都必須要更新兩個地方。每次由于新的知識導致一個對象被修改時,也必須重新分析和修改另一個對象。如果不進行實際的重新分析,結果就會出現同一個概念的兩個版本,它們遵守不同的規(guī)則,甚至不同的數據。更重要的是,團隊成員必須學習同一操作的兩種方法,以及保持這兩種方法同步的各種方式。假同源是指使用相同術語(或已實現的對象)的兩個人認為他們是在談論同一件事情,但實際上并不是這樣。但是,當兩個定義都與同一個領域方面相關,而只是在概念上稍有區(qū)別時,這種沖突更難以發(fā)現。假同源會導致開發(fā)團隊互相干擾對方的代碼,也可能導致數據庫中含有奇怪的矛盾,還會引起團隊溝通的混淆。注意用詞詞匯注意正確用詞,不要歪曲詞義開發(fā)人員經常習慣于使用增/刪/改/查(CRUD)此類動詞詞匯,也許有時候它們也確實屬于通用語言,但大多數情況下,它們并不能正確反映業(yè)務,用詞上混淆了業(yè)務概念。當前第26頁\共有142頁\編于星期三\12點模型的分裂在理想的世界中,我們可以有一種把整個企業(yè)領域包含進來的單一模型;這個模型將是統(tǒng)一的,沒有任何相互矛盾或相互重疊的術語定義;每個有關領域的邏輯聲明都將是一致的。但大型系統(tǒng)開發(fā)并不是這樣理想。大型系統(tǒng)領域模型的完全統(tǒng)一是不可行的,也不是一種經濟有效的做法。我們可以采用限界上下文(BoundedContext)定義每個模型的應用范圍,采用上下文映射(ContextMap)給出項目上下文以及它們之間關系的總體視圖。任何一個大型項目都會存在多個模型。而當基于不同模型的代碼被組合到一起后,軟件就會出現bug、變得不可靠和難以理解。團隊成員之間的溝通變得混亂。人們往往弄不清楚一個模型不應該在哪個上下文中使用。明確地定義模型所應用的上下文。根據團隊的組織、軟件系統(tǒng)的各個部分的用法以及物理表現(代碼和數據庫模式等)來設置模型的邊界。在這些邊界中嚴格保持模型的一致性,而不要受到邊界之外問題的干擾和混淆。在Context中,要保證模型在邏輯上統(tǒng)一,而不用考慮它是不是適用于邊界之外的情況。在其他Context中,會使用其他的模型,這些模型具有不同的術語、概念、規(guī)則和UBIQUITOUSLANGUAGE的技術行話。定義BoundedContext:視察項目的現狀,而不是它的理想狀態(tài)。當前第27頁\共有142頁\編于星期三\12點領域、子域和限界上下文當前第28頁\共有142頁\編于星期三\12點核心域、支撐域和通用域ACoreDomainisapartofthebusinessDomainthatisofprimaryimportancetothesuccessoftheorganization.Itisofutmostimportancetotheongoingsuccessofthebusiness.Ifadomainmodelssomeaspectofthebusinessthatisessential,yetnotCore,itisaSupportingSubdomain.ifadomaincapturesnothingspecialtothebusiness,yetisrequiredfortheoverallbusinesssolution,itisaGenericSubdomain.Focusonthecoredomain當前第29頁\共有142頁\編于星期三\12點戰(zhàn)術建模與戰(zhàn)略建模當前第30頁\共有142頁\編于星期三\12點領域驅動設計的綜合應用當前第31頁\共有142頁\編于星期三\12點共享內核(SharedKernel)當不同團隊開發(fā)一些緊密相關的應用程序時,如果團隊之間不進行協調,即使短時間內能夠取得快速進展,他們開發(fā)出的產品也可能互相不適合,最后可能不得不在轉換層上花費大量時間,而且得到的產品也五花八門。從領域模型中選出兩個團隊都同意共享的一個子集。當然,除了模型的這個子集以外,這還包括與該模型部分相關的代碼子集,或數據庫設計的子集。這部分明確共享的內容具有特殊的狀態(tài),而且一個團隊在沒與另一個團隊商量的情況下不應擅自更改它。功能系統(tǒng)要經常進行集成,但集成的頻率應該比團隊中ContinuousIntegration的頻率低一些。在進行這些集成的時候,兩個團隊都要運行測試。SharedKernel通常是CoreDomain,或是一組GenericSubdomain(通用子領域),也可能二者兼有。當前第32頁\共有142頁\編于星期三\12點企業(yè)架構方法與領域驅動設計架構內容框架企業(yè)連續(xù)系列架構開發(fā)方法架構開發(fā)指引和技術參考模型架構能力框架兩者都強調Business和IT的高度統(tǒng)一,很多企業(yè)架構方法對于領域驅動設計“戰(zhàn)略設計”的具體實施辦法具有詳實的指導意義。如TOGAFV9構件:當前第33頁\共有142頁\編于星期三\12點eTOM業(yè)務建模Level0ProcessesLevel1ProcessesLevel2Processes……業(yè)務流程解耦/分解當前第34頁\共有142頁\編于星期三\12點eTOM業(yè)務建模BSS業(yè)務流程框架領域解決特定問題當前第35頁\共有142頁\編于星期三\12點eTOM信息數據模型eTOM0級視圖SID1級視圖ABE:AggregateBusinessEntity,ABE是SID中一組定義良好的實體,具有高內聚、低耦合的特征。共享內核當前第36頁\共有142頁\編于星期三\12點eTOM信息數據模型當前第37頁\共有142頁\編于星期三\12點參考讀物《領域驅動設計—軟件核心復雜性應對之道》及《實現領域驅動設計》中的相關章節(jié)《軟件方法-業(yè)務建模和需求》第三章“業(yè)務建?!敝械南嚓P內容參考模型范例:TMForum的eTOM模型:

當前第38頁\共有142頁\編于星期三\12點培訓內容領域驅動設計簡介領域通用語言領域驅動設計的構造塊領域驅動設計編程實踐CQRS架構模型驅動開發(fā)當前第39頁\共有142頁\編于星期三\12點領域驅動設計的構造塊當前第40頁\共有142頁\編于星期三\12點Entity(實體)實體是一個具有唯一身份標識的對象,并且可以在相當長的一段時間內持續(xù)地變化。我們可以對實體做多次修改,故一個實體對象可能和它先前的對象大不相同,但是由于它們擁有相同的身份標識(identity),它們依然是同一個實體。我們通過標識對對象進行區(qū)分,而不是屬性,此時我們應該將標識作為主要的模型定義。同時我們需要保持簡單的類定義,并且關注對象在其生命周期中的連續(xù)性和唯一標識性。隨著對象的改變,我們可能會跟蹤這樣的改變,比如什么時候發(fā)生了改變,發(fā)生了什么改變,是誰做出的改變等。我們應該慎重對待在對象整個生命周期中所發(fā)生的合法改變。唯一的身份標識和可變性(mutability)特征將實體對象和值對象(ValueObjects)區(qū)分開來。很多時候,一個領域概念應該建模成值對象,而不是實體對象。實體和值對象是領域模型概念,而不是數據存儲模型概念。當前第41頁\共有142頁\編于星期三\12點ValueObjects(值對象)值對象的特征它度量或者描述了領域中的一件東西。它可以作為不變量。它將不同的相關的屬性組合成一個概念整體當度量和描述改變時,可以用另一個值對象予以替換它可以和其他值對象進行相等性比較它不會對協作對象造成副作用。當我們只關心一個模型元素的屬性時,應把它歸類為值對象。我們應該使這個模型元素能夠表示出其屬性的意義,并為它提供相關功能。值對象應該是不可變的。不要為它分配任何標識,而且不要把它設計成Entity那么復雜。應該盡量使用值對象來建模而不是實體對象,即便一個領域概念必須建模成實體,在設計時也應該更偏向于將其作為值對象容器,而不是子實體容器。實體對象與值對象是領域概念,而不是數據存儲模型概念值對象可以與其所在的實體對象保存在同一張表中,值對象的每一個屬性保存為一列;值對象也可以獨立于其所在的實體對象保存在另一張表中,值對象獲得委派主鍵,該主鍵對客戶端是不可見的。當前第42頁\共有142頁\編于星期三\12點Entity和ValueObject示例當前第43頁\共有142頁\編于星期三\12點Aggregates(聚合)在具有復雜關聯的模型中,要想保證對象更改的一致性是很困難的。不僅互不關聯的對象需要遵守一些固定規(guī)則,而且緊密關聯的各組對象也要遵守一些固定規(guī)則。然而,過于謹慎的鎖定機制又會導致多個用戶之間毫無意義地互相關繞,從而使系統(tǒng)不可用。在任何具有持久化數據存儲的系統(tǒng)中,對數據進行修改的事務必須要有一個范圍,而且要有一種保持數據一致性的方式。聚合(Aggregate)是一組相關對象的集合,我們把它作為數據修改的單元。每個聚合都有一個根和一個邊界,邊界定義了聚合的內部都有什么,根則是聚合中所包含的一個特定實體。在聚合中,根是唯一允許外部對象保持對它的引用的元素,而邊界內部的對象之間則可以互相引用。除根以外的其他Entity都有本地表示,但這些標識只有在聚合內部才需要加以區(qū)別,因為外部對象除了根Entity之外看不到其他對象。聚合行為視為是一個整體,在每個事務完成時,必須要滿足聚合內所應用的固定規(guī)則的要求,即保證數據變化的一致性。根實體最終檢查固定規(guī)則;刪除操作必須一次刪除聚合邊界之內的所有對象;當提交對聚合邊界內部的任何對象的修改時,整個聚合中的所有固定規(guī)則都必須被滿足。原則:在一致性邊界之內建模真正的不變條件;設計小聚合;通過唯一標識引用其他聚合;在邊界之外使用最終一致性盡量將根實體所包含的其他聚合建模成值對象,而不是實體。當前第44頁\共有142頁\編于星期三\12點Aggregates(聚合)示例當前第45頁\共有142頁\編于星期三\12點DomainEvent(領域事件)DomainEvent(領域事件)有時候應用需要記錄跟蹤事情的發(fā)生領域事件經常被建模為ValueObject,但這些ValueObject并不能被共享,因為領域事件本身是“唯一”的。一個領域事件是指一個在領域中“有意義”的事件HintsUML四色原型中有一個相近概念,稱為時刻-時段原型(Moment-interval),即表示事物在某個時刻或某一段時間內發(fā)生。當前第46頁\共有142頁\編于星期三\12點參考:四色原型四色原型是誕生于90年代,現在被廣泛使用的一種系統(tǒng)分析方法,如Borland的Together架構師版,準確地說,是由PeterCoad和MarkMayfield首先提出,然后由DavidNorth拓展。當前第47頁\共有142頁\編于星期三\12點Repositories(資源庫/倉儲)客戶需要以一種符合實際的方式來獲取對以存在的領域對象的引用。為每種需要全局訪問的對象類型創(chuàng)建一個對象,這個對象就相當于該類型的所有對象在內存中的一個集合的“替身”。通過一個眾所周知的接口來提供訪問。提供添加和刪除對象的方法,用這些方法來封裝在數據存儲中實際插入或刪除數據的操作。提供根據具體標準來挑選對象的方法,并返回屬性值滿足查詢標準的對象或對象集合(所返回的對象是完全實例化的),從而將實際的存儲和查詢技術封裝起來。只為那些確實需要直接訪問的聚合提供Repository。讓客戶始終聚焦于模型,而將所有對象的存儲和訪問操作交給Repository來完成。Repository的接口應當采用領域通用語言。作為客戶端,不應當知道數據庫實現的細節(jié)。Repository和DAO的作用類似,二者的主要區(qū)別:DAO是比Repository更低的一層,包含了如何從數據庫中提取數據的代碼。Repository以“領域”為中心,所描述的是“領域語言”。Repository把ORM框架與領域模型隔離,對外隱藏封裝了數據訪問機制。當前第48頁\共有142頁\編于星期三\12點Repositories(資源庫/倉儲)示例publicinterfaceAccountRepository{AccountfindAccount(StringaccountId);

voidaddAccount(Accountaccount);}publicclassHibernateAccountRepositoryimplementsAccountRepository{privateHibernateTemplatehibernateTemplate;publicHibernateAccountRepository(HibernateTemplatetemplate){hibernateTemplate=template;}publicvoidaddAccount(Accountaccount){hibernateTemplate.save(account);}publicAccountfindAccount(finalStringaccountId){return(Account)DataAccessUtils.uniqueResult(hibernateTemplate.findByNamedQueryAndNamedParam(“Account.findAccountByAccountId”,“accountId”,accountId));}}當前第49頁\共有142頁\編于星期三\12點Services(領域服務)當領域中的某個操作過程或轉換過程不是實體或值對象的職責時,我們便應該將該操作放在一個單獨的接口中,即領域服務。如果勉強地把這些重要的領域功能歸為Entity或ValueObject的職責,那么不是歪曲了基于模型的對象的定義,就是人為地增加了一些無意義的對象。應確保領域服務和通用語言是一致的,并且保證它是無狀態(tài)的。正確區(qū)分領域服務(DomainService)和應用服務(ApplicationService):我們不應把業(yè)務邏輯置于應用服務,但我們會把業(yè)務邏輯置于領域服務中。(應用)服務要做“薄”。領域服務職責:跨聚合實例業(yè)務邏輯;沒辦法合理放到實體中的其它業(yè)務邏輯。應用服務職責:跨限界上下文的業(yè)務邏輯;DTO轉換;事務AOP、權限AOP、日志AOP、異常AOP;外部系統(tǒng)訪問(郵件、消息隊列)。領域服務設計原則:用來組織業(yè)務邏輯,面向業(yè)務邏輯;細粒度;內部視圖看系統(tǒng);一個請求對應多個服務的多個方法;服務之間會存在依賴;應用服務設計原則:用來封裝業(yè)務邏輯;面向用例;粗粒度;外部視圖看系統(tǒng);一個請求對應一個方法;服務之間互不依賴。應用服務和領域服務區(qū)分非常敏感,有時候需要在快速性/方便性上做折衷。當前第50頁\共有142頁\編于星期三\12點Services(領域服務)示例publicinterfaceMoneyTransferService{BankingTransactiontransfer(StringfromAccountId,StringtoAccountId,doubleamount);}publicclassMoneyTransferServiceImplimplementsMoneyTransferService{privatefinalAccountRepositoryaccountRepository;

privatefinalBankingTransactionRepositorybankingTransactionRepository;publicMoneyTransferServiceImpl(AccountRepositoryaccountRepository,

BankingTransactionRepositorybankingTransactionRepository){…}BankingTransactiontransfer(StringfromAccountId,StringtoAccountId,

doubleamount){…}}當前第51頁\共有142頁\編于星期三\12點應用服務、領域服務和基礎設施服務當前第52頁\共有142頁\編于星期三\12點Factories(工廠)當創(chuàng)建一個對象或創(chuàng)建整個聚合時,如果創(chuàng)建工作很復雜,或者暴露了過多的內部結構,則可以使用Factory進行封裝。應該將創(chuàng)建復雜對象的實例和聚合的職責轉移到一個單獨的對象,這個對象本身在領域模型中可能沒有職責,但它仍是領域設計的一部分。不同類型的工廠模式:工廠類工廠方法當前第53頁\共有142頁\編于星期三\12點Modules(模塊)Module為人們提供了兩種觀察模型的方式,一是可以在Module中查看細節(jié),而不會被整個模型淹沒,二是觀察Module之間的關系,而不考慮其內部細節(jié)。模塊之間應該是低耦合的,而在模塊內部則是高內聚的。模塊并不僅僅是代碼的劃分,而且也是概念的劃分。一個人一次考慮的事情是有限的(因此才有低耦合);不連貫的思想和“一鍋粥”似的思想同樣難于理解(因此才有高內聚)。選擇能夠描述系統(tǒng)的Module,并使之包含一個內聚的概念集合。這通常會實現Module之間的低耦合,但如果效果不理想,則應尋找一種更改模型的方式來消除概念之間的耦合,或者找到一個可作為Module基礎的概念,基于這個概念組織的模型可以以一種有意義的方式將元素集中到一起。找到一種低耦合的概念組織方式,從而可以相互獨立地理解和分析這些概念。對模型進行精化,直到可以根據高層領域概念對模型進行劃分,同時相應的代碼也不會產生耦合。Module的名稱應該是領域通用語言中的術語。模塊及其名稱應反映出領域的深層知識。當前第54頁\共有142頁\編于星期三\12點培訓內容領域驅動設計簡介領域通用語言領域驅動設計的構造塊領域驅動設計編程實踐CQRS架構模型驅動開發(fā)當前第55頁\共有142頁\編于星期三\12點概念辨析-VO/DTO/DO/PO(一)ViewObject(視圖對象):視圖對象,用于展示層,其作用是把某個指定頁面(或組件)的所有數據封裝起來。DataTransferObject(數據傳輸對象):這個概念來源于J2EE的設計模式,原來的目的是為了EJB的分布式應用提供粗粒度的數據實體,以減少分布式調用的次數,從而提高分布式調用的性能和降低網絡負載,但在這里,我泛指用于展示層與服務層之間的數據傳輸對象。DomainObject(領域對象):從現實世界中抽象出來的有形或無形的業(yè)務實體、值對象或領域服務。PersistentObject(持久化對象):跟持久層(通常是關系型數據庫)的數據結構形成一一對應的映射關系,如果持久層是關系型數據庫,那么,數據表中的每個字段(或若干個)就對應PO的一個(或若干個)屬性。Ref:http://

當前第56頁\共有142頁\編于星期三\12點概念辨析-VO/DTO/DO/PO(二)VO與DTO:絕大多數應用場景下,VO與DTO的屬性值基本一致,但對于設計層面來說,概念上還是存在VO和DTO的區(qū)別,DTO代表服務層需要接收的數據和返回的數據,而VO代表展示層需要顯示的數據。示例:服務層有一個getUser的方法返回一個系統(tǒng)用戶,其中有一個屬性是gender(性別),對于服務層來說,它只從語義上定義:1-男性,2-女性,0-未指定,而對于展示層來說,它可能需要用“帥哥”代表男性,用“美女”代表女性,用“秘密”代表未指定。說到這里,可能你還會反駁,在服務層直接就返回“帥哥美女”不就行了嗎?對于大部分應用來說,這不是問題,但設想一下,如果需求允許客戶可以定制風格,而不同風格對于“性別”的表現方式不一樣,又或者這個服務同時供多個客戶端使用(不同門戶),而不同的客戶端對于表現層的要求有所不同,那么,問題就來了。再者,回到設計層面上分析,從職責單一原則來看,服務層只負責業(yè)務,與具體的表現形式無關,因此,它返回的DTO,不應該出現與表現形式的耦合。實現層面是否需要區(qū)分二者概念?具體問題具體分析當前第57頁\共有142頁\編于星期三\12點概念辨析-VO/DTO/DO/PO(三)DTO與DO:DTO是展示層和服務層之間的數據傳輸對象(可以認為是兩者之間的協議),而DO是對現實世界各種業(yè)務角色的抽象,這就引出了兩者在數據上的區(qū)別,例如UserInfo和User,對于一個getUser方法來說,本質上它永遠不應該返回用戶的密碼,因此UserInfo至少比User少一個password的數據。而在領域驅動設計中,DO不是簡單的POJO,它具有領域業(yè)務邏輯。在設計層面,展示層向服務層傳遞的DTO與服務層返回給展示層的DTO在概念上是不同的(如返回UserInfo應該不包含password,但創(chuàng)建User傳入的參數需要包含password),但在實現層面,我們通常很少會這樣做(定義兩個UserInfo,甚至更多),因為這樣做并不見得很明智,我們完全可以設計一個完全兼容的DTO,在服務層接收數據的時候,不該由展示層設置的屬性(如訂單的總價應該由其單價、數量、折扣等決定),無論展示層是否設置,服務層都一概忽略,而在服務層返回數據時,不該返回的數據(如用戶密碼),就不設置對應的屬性。為什么不在服務層中直接返回DO:DO具有一些不應該讓展示層知道的數據;DO具有業(yè)務方法,如果直接把DO傳遞給展示層,展示層的代碼就可以繞過服務層直接調用它不應該訪問的操作,對于基于AOP攔截服務層來進行訪問控制的機制來說,這問題尤為突出,而在展示層調用DO的業(yè)務方法也會因為事務的問題,讓事務難以控制;ORM框架(如Hibernate)“延遲加載”技術,如果直接把DO暴露給展示層,對于大部分情況,展示層不在事務范圍之內,如果其嘗試在Session關閉的情況下獲取一個未加載的關聯對象,會出現運行時異常(對于Hibernate來說,就是LazyInitiliaztionException);從設計層面來說,展示層依賴于服務層,服務層依賴于領域層,如果把DO暴露出去,就會導致展示層直接依賴于領域層,這雖然依然是單向依賴,但這種跨層依賴會導致不必要的耦合。DTO應該是一個“扁平的二維對象”當前第58頁\共有142頁\編于星期三\12點概念辨析-VO/DTO/DO/PO(四)DO與PO:DO和PO在絕大部分情況下是一一對應的,PO是只含有get/set方法的POJO,但某些場景還是能反映出兩者在概念上存在本質的區(qū)別。DO在某些場景下不需要進行顯式的持久化,例如利用策略模式設計的商品折扣策略,會衍生出折扣策略的接口和不同折扣策略實現類,這些折扣策略實現類可以算是DO,但它們只駐留在靜態(tài)內存,不需要持久化到持久層,因此,這類DO是不存在對應的PO的。同樣的道理,某些場景下,PO也沒有對應的DO,例如老師Teacher和學生Student存在多對多的關系,在關系數據庫中,這種關系需要表現為一個中間表,也就對應有一個TeacherAndStudentPO的PO,但這個PO在業(yè)務領域沒有任何現實的意義,它完全不能與任何DO對應上。這里要特別聲明,并不是所有多對多關系都沒有業(yè)務含義,這跟具體業(yè)務場景有關,例如:兩個PO之間的關系會影響具體業(yè)務,并且這種關系存在多種類型,那么這種多對多關系也應該表現為一個DO,又如:“角色”與“資源”之間存在多對多關系,而這種關系很明顯會表現為一個DO——“權限”。某些情況下,為了某種持久化策略或者性能的考慮,一個PO可能對應多個DO,反之亦然。例如客戶Customer有其聯系信息Contacts,這里是兩個一對一關系的DO,但可能出于性能的考慮(極端情況,權作舉例),為了減少數據庫的連接查詢操作,把Customer和Contacts兩個DO數據合并到一張數據表中。反過來,如果一本圖書Book,有一個屬性是封面cover,但該屬性是一副圖片的二進制數據,而某些查詢操作不希望把cover一并加載,從而減輕磁盤IO開銷,同時假設ORM框架不支持屬性級別的延遲加載,那么就需要考慮把cover獨立到一張數據表中去,這樣就形成一個DO對應對個PO的情況。PO的某些屬性值對于DO沒有任何意義,這些屬性值可能是為了解決某些持久化策略而存在的數據,例如為了實現“樂觀鎖”,PO存在一個version的屬性,這個version對于DO來說是沒有任何業(yè)務意義的,它不應該在DO中存在。同理,DO中也可能存在不需要持久化的屬性?,F在的業(yè)務應用開發(fā),基本上不需要區(qū)分DO與PO,PO完全可以通過JPA,HibernateAnnotations/hbm隱藏在DO之中。當前第59頁\共有142頁\編于星期三\12點概念辨析-VO/DTO/DO/PO(五)VO/DTO/DO/PO轉換:工廠類或工廠方法;構造函數;工具類,如BeanUtils;轉換實現中應注意類層次概念的依賴關系。DODTOVOPO當前第60頁\共有142頁\編于星期三\12點Entity(一)Entity的標識生成:用戶提供應用程序生成持久化機制生成另一個限界上下文提供在JPA中,有下面四種策略:容器自動生成

(GenerationType.AUTO):由JPA自動生成使用數據庫的自動增長字段生成(GenerationType.IDENTITY):需要數據庫支持根據數據庫序列號(GenerationType.SEQUENCE):Oracle支持對序列號的支持使用數據庫表的字段生成(GenerationType.TABLE):使用數據庫中指定表的某個字段記錄實體對象的標識,通過該字段的增長為新增加的實體對象賦唯一值。當前第61頁\共有142頁\編于星期三\12點Entity(二)繼承關系:因為關系數據庫的表之間不存在繼承關系,Entity提供三種基本的繼承映射策略:SingleTableJoinedTableperClassRef:當前第62頁\共有142頁\編于星期三\12點Entity(三)繼承關系:SingleTable@SuppressWarnings("serial")@Entity@Table(name="Vehicle_Hierarchy")@Inheritance(strategy=InheritanceType.SINGLE_TABLE)@DiscriminatorColumn(name="Discriminator",discriminatorType=DiscriminatorType.STRING,length=30)@DiscriminatorValue("Vehicle")publicclassVehicleimplementsSerializable{

//基類

privateLongid;

privateShortspeed;//速度

@Id

@GeneratedValue

@Column(columnDefinition="integer")//指定使用適配Integer長度的數據類型

publicLonggetId(){

returnid;

}

publicvoidsetId(Longid){

this.id=id;

}}@SuppressWarnings("serial")@Entity@DiscriminatorValue("Car")publicclassCarextendsVehicle{

//Vehicle的子類

privateStringengine;//發(fā)動機

@Column(nullable=true,length=30)

publicStringgetEngine(){

returnengine;

}publicvoidsetEngine(Stringengine){

this.engine=engine;

}

}@SuppressWarnings("serial")@Entity@DiscriminatorValue("Camion")publicclassCamionextendsCar{

//Car的子類

privateStringcontainer;//集裝箱

@Column(nullable=true,length=30)

publicStringgetContainer(){

returncontainer;}

publicvoidsetContainer(Stringcontainer){

this.container=container;}}當前第63頁\共有142頁\編于星期三\12點Entity(四)繼承關系:Joined@SuppressWarnings(“serial”)@Entity@Inheritance(strategy=InheritanceType.JOINED)@Table(name="Vehicle")publicclassVehicleimplementsSerializable{//基類

privateLongid;privateShortspeed;//速度

@Id@GeneratedValue@Column(columnDefinition="integer")publicLonggetId(){returnid;}publicvoidsetId(Longid){this.id=id;}publicShortgetSpeed(){returnspeed;}publicvoidsetSpeed(Shortspeed){this.speed=speed;}}@SuppressWarnings("serial")@Entity@Table(name="Car")@PrimaryKeyJoinColumn(name="CarID")//把主鍵對應的列名更改為CarID</STRONG>publicclassCarextendsVehicle{//Vehicle的子類privateStringengine;//發(fā)動機@Column(nullable=true,length=30)publicStringgetEngine(){returnengine;}publicvoidsetEngine(Stringengine){this.engine=engine;}}@SuppressWarnings("serial")@Entity@Table(name="Camion")@PrimaryKeyJoinColumn(name="CamionID")//把主鍵對應的列名更改為CamionID</STRONG>publicclassCamionextendsCar{//Car的子類

privateStringcontainer;@Column(nullable=true,length=30)publicStringgetContainer(){returncontainer;}publicvoidsetContainer(Stringcontainer){this.container=container;}}當前第64頁\共有142頁\編于星期三\12點Entity(五)繼承關系:TableperClass一旦使用這種策略,意味著你不能使用AUTOgenerator和IDENTITYgenerator,即主鍵值不能采用數據庫自動生成。@SuppressWarnings("serial")@Entity//或@MappedSuperclass@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)@Table(name=“Vehicle”)//當為MappedSuperclass時,不要@Table標注publicclassVehicleimplementsSerializable{

//基類

privateLongid;

privateShortspeed;//速度

@Id

@Column(columnDefinition="integer")

publicLonggetId(){

returnid;

}

publicvoidsetId(Longid){

this.id=id;

}

publicShortgetSpeed(){

returnspeed;

}

publicvoidsetSpeed(Shortspeed){

this.speed=speed;

}}@SuppressWarnings("serial")@Entity@Table(name="Car")publicclassCarextendsVehicle{

//Vehicle的子類

privateStringengine;//發(fā)動機

@Column(nullable=true,length=30)

publicStringgetEngine(){

returnengine;

}

publicvoidsetEngine(Stringengine){

this.engine=engine;

}}@SuppressWarnings("serial")@Entity@Table(name="Camion")publicclassCamionextendsCar{

//Car的子類

privateStringcontainer;//集裝箱

@Column(nullable=true,length=30)

publicStringgetContainer(){

returncontainer;

}

publicvoidsetContainer(Stringcontainer){

this.container=container;

}}當前第65頁\共有142頁\編于星期三\12點Entity(六)審計(Audit):最近一次修改時間,最近一次修改人。@Entity@EntityListeners({JodaAuditListener.class})publicclassCargoextendsAbstractDomainObjectimplementsJodaAuditable,Identifiable{…}publicinterfaceJodaAuditable{ publicvoidsetCreatedBy(StringcreatedBy); publicStringgetCreatedBy(); publicvoidsetCreatedDate(DateTimecreatedDate); publicDateTimegetCreatedDate();publicvoidsetLastUpdatedBy(StringupdatedBy);publicStringgetLastUpdatedBy();publicvoidsetLastUpdated(DateTimeupdateDate);publicDateTimegetLastUpdated();}publicclassJodaAuditListener{@PreUpdate@PrePersistprivatevoidchangeAuditInformation(JodaAuditableauditableEntity){ DateTimelastUpdated=newDateTime();auditableEntity.setLastUpdated(lastUpdated);StringlastUpdatedBy=ServiceContextStore.getCurrentUser();auditableEntity.setLastUpdatedBy(lastUpdatedBy);if(auditableEntity.getCreatedDate()==null)auditableEntity.setCreatedDate(lastUpdated);if(auditableEntity.getCreatedBy()==null)auditableEntity.setCreatedBy(lastUpdatedBy);}}當前第66頁\共有142頁\編于星期三\12點Entity(七)審計(Audit):使用事件記錄實體狀態(tài)變更事件,操作人以及狀態(tài)改變內容等。@Service("bettingService")publicclassBettingServiceImplimplementsBettingService{ privatestaticfinalLoggerLOG=LoggerFactory.getLogger(BettingServiceImpl.class); …

@Publish(eventType=BettingInstruction.class,topic="bettingInstructionTopic",eventBus="commandBus") publicvoidplaceBet(Betbet){ LOG.info("###Placingbet:{}",bet); //dosomeinitialvalidation... //newBettingInstructionwillbepublished }}@Subscribe(topic="bettingInstructionTopic",eventBus="commandBus")public

classBettingEngineImplimplementsBettingEngine{ … publicvoidreceive(Eventevent){ DynamicMethodDispatcher.dispatch(this,event,"handle"); } publicvoidhandle(BettingInstructionbetInstruction){ LOG.info("###Handlingbet:{}",betInstruction); instructionRepository.save(betInstruction);

… }}當前第67頁\共有142頁\編于星期三\12點Entity(八)并發(fā)沖突:樂觀鎖@Entity@EntityListeners({JodaAuditListener.class})publicclassCargoextendsAbstractDomainObjectimplementsJodaAuditable,Identifiable{

@Version @Column(name="VERSION",nullable=false) privateLongversion; …}當前第68頁\共有142頁\編于星期三\12點ValueObject(一)值對象可以與其所在的實體對象保存在同一張表中,值對象的每一個屬性保存為一列;值對象也可以獨立于其所在的實體對象保存在另一張表中,值對象獲得委派主鍵,該主鍵對客戶端是不可見的。當前第69頁\共有142頁\編于星期三\12點ValueObject(二)值對象可以與其所在的實體對象保存在同一張表中:@Entity@EntityListeners({JodaAuditListener.class})publicclas

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論