九、提交訂單頁(yè)面顯示功能
19.1在templates/cart.html文件中設(shè)置表單提交:
19.2在order/urls.py中配置對(duì)應(yīng)的url:
19.3在order/view.py中編寫OrderPlaceView類顯示訂單頁(yè)面:
19.4在templates/place_order.html文件中完成訂單頁(yè)面的顯示:
19.5在templates/order_place.html文件中完成創(chuàng)建訂單功能:
19.6在order/urls.py文件中定義提交訂單的url:
19.7數(shù)據(jù)庫(kù)MySQL的事務(wù):
19.7.1事務(wù)概念: 一組mysql語(yǔ)句,要么執(zhí)行,要么全不不執(zhí)行。
19.7.2事務(wù)特點(diǎn):
1、原子性:一組事務(wù),要么成功;要么撤回。
2、穩(wěn)定性 :有非法數(shù)據(jù)(外鍵約束之類),事務(wù)撤回。
3、隔離性:事務(wù)獨(dú)立運(yùn)行。一個(gè)事務(wù)處理后的結(jié)果,影響了其他事務(wù),那么其他事務(wù)會(huì)撤回。事務(wù)的100%隔離,需要犧牲速度。
4、可靠性:軟、硬件崩潰后,InnoDB數(shù)據(jù)表驅(qū)動(dòng)會(huì)利用日志文件重構(gòu)修改??煽啃院透咚俣炔豢杉娴?, innodb_flush_log_at_trx_commit 選項(xiàng) 決定什么時(shí)候吧事務(wù)保存到日志里。
19.7.3事務(wù)控制語(yǔ)句:
COMMIT;COMMIT會(huì)提交事務(wù),并使已對(duì)數(shù)據(jù)庫(kù)進(jìn)行的所有修改稱為永久性的;
ROLLBACK;回滾會(huì)結(jié)束用戶的務(wù),并撤銷正在進(jìn)行的所有未提交的修改;
SAVEPOINT identifier;SAVEPOINT允許在事務(wù)中創(chuàng)建一個(gè)保存點(diǎn),一個(gè)事務(wù)中可以有多個(gè)SAVEPOINT;
RELEASE SAVEPOINT identifier;刪除一個(gè)事務(wù)的保存點(diǎn),當(dāng)沒(méi)有指定的保存點(diǎn)時(shí),執(zhí)行該語(yǔ)句會(huì) 拋出一個(gè)異常;
ROLLBACK TO identifier;把事務(wù)回滾到標(biāo)記點(diǎn);
19.7.4事務(wù)隔離級(jí)別:
Read Uncommitted(讀取未提交內(nèi)容):在該隔離級(jí)別,所有事務(wù)都可以看到其他未提交事務(wù)的執(zhí)行結(jié)果。本隔離級(jí)別很少用于實(shí)際應(yīng)用,因?yàn)樗男阅芤膊槐绕渌?jí)別好多少。讀取未提交的數(shù)據(jù),也被稱之為臟讀(Dirty Read)。
Read Committed(讀取提交內(nèi)容):這是大多數(shù)數(shù)據(jù)庫(kù)系統(tǒng)的默認(rèn)隔離級(jí)別(但不是MySQL默認(rèn)的)。它滿足了隔離的簡(jiǎn)單定義:一個(gè)事務(wù)只能看見已經(jīng)提交事務(wù)所做的改變。這種隔離級(jí)別 也支持所謂的不可重復(fù)讀(Nonrepeatable Read),因?yàn)橥皇聞?wù)的其他實(shí)例在該實(shí)例處理其間可能會(huì)有新的commit,所以同一select可能返回不同結(jié)果。
Repeatable Read(可重讀):這是MySQL的默認(rèn)事務(wù)隔離級(jí)別,它確保同一事務(wù)的多個(gè)實(shí)例在并發(fā)讀取數(shù)據(jù)時(shí),會(huì)看到同樣的數(shù)據(jù)行。不過(guò)理論上,這會(huì)導(dǎo)致另一個(gè)棘手的問(wèn)題:幻讀 (Phantom Read)。
Serializable(可串行化):這是最高的隔離級(jí)別,它通過(guò)強(qiáng)制事務(wù)排序,使之不可能相互沖突,從而解決幻讀問(wèn)題。簡(jiǎn)言之,它是在每個(gè)讀的數(shù)據(jù)行上加上共享鎖。在這個(gè)級(jí)別,可能導(dǎo)致大量的超時(shí)現(xiàn)象和鎖競(jìng)爭(zhēng)。
19.8提交訂單功能的業(yè)務(wù)分析:
19.9訂單并發(fā)處理:
悲觀鎖:獲取數(shù)據(jù)時(shí)對(duì)數(shù)據(jù)行了鎖定,其他事務(wù)要想獲取鎖,必須等原事務(wù)結(jié)束。
使用示例:select * from df_goods_sku where id=17 for update;
樂(lè)觀鎖:查詢時(shí)不鎖數(shù)據(jù),提交更改時(shí)進(jìn)行判斷.
使用示例:update df_goods_sku set stock=0, sales=1 where id=17 and stock=1;
19.9.1項(xiàng)目使用原則:
沖突比較少的時(shí)候,使用樂(lè)觀鎖。
沖突比較多的時(shí)候,使用悲觀鎖。
19.10在order/views.py中定義OrderCommitView訂單創(chuàng)建類:
二十、用戶中心的訂單頁(yè)面顯示
20.1在user/urls.py中定義用戶中心訂單頁(yè)的url:
20.2在user/view.py中定義UserOrderView類:
20.3在templates/user_center_order.html中顯示數(shù)據(jù):
天的企業(yè)應(yīng)用程序無(wú)疑是復(fù)雜的,并依賴一些專門技術(shù)(持久性,AJAX,Web服務(wù)等)來(lái)完成它們的工作。作為開發(fā)人員,我們傾向于關(guān)注這些技術(shù)細(xì)節(jié)是可以理解的。但事實(shí)是,一個(gè)不能解決業(yè)務(wù)需求的系統(tǒng)對(duì)任何人都沒(méi)有用,無(wú)論它看起來(lái)多么漂亮或者如何很好地構(gòu)建其基礎(chǔ)設(shè)施。
領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)的理念 - 首先由Eric Evans在他的同名書[1]中描述 - 是關(guān)于將我們的注意力放在應(yīng)用程序的核心,關(guān)注業(yè)務(wù)領(lǐng)域固有的復(fù)雜性本身。我們還將核心域(業(yè)務(wù)獨(dú)有)與支持子域(通常是通用的,如金錢或時(shí)間)區(qū)分開來(lái),并將更多的設(shè)計(jì)工作放在核心上。
域驅(qū)動(dòng)設(shè)計(jì)包含一組用于從域模型構(gòu)建企業(yè)應(yīng)用程序的模式。在您的軟件生涯中,您可能已經(jīng)遇到過(guò)許多這樣的想法,特別是如果您是OO語(yǔ)言的經(jīng)驗(yàn)豐富的開發(fā)人員。但將它們一起應(yīng)用將允許您構(gòu)建真正滿足業(yè)務(wù)需求的系統(tǒng)。
代碼和模型......
使用DDD,我們希望創(chuàng)建問(wèn)題域的模型。持久性,用戶界面和消息傳遞的東西可以在以后出現(xiàn),這是需要理解的領(lǐng)域,因?yàn)檎跇?gòu)建的系統(tǒng)中,可以區(qū)分公司的業(yè)務(wù)與競(jìng)爭(zhēng)對(duì)手。 (如果不是這樣,那么考慮購(gòu)買包裝產(chǎn)品)。
按模型,我們不是指圖表或一組圖表;確定,圖表很有用,但它們不是模型,只是模型的不同視圖(參見圖)。不,模型是我們選擇在軟件中實(shí)現(xiàn)的概念集,以代碼和用于構(gòu)建交付系統(tǒng)的任何其他軟件工件表示。換句話說(shuō),代碼就是模型。文本編輯器提供了一種使用此模型的方法,盡管現(xiàn)代工具也提供了大量其他可視化(UML類圖,實(shí)體關(guān)系圖,Spring beandocs [2],Struts / JSF流等)。
Figure 1: Model vs Views of the Model
這是DDD模式的第一個(gè):模型驅(qū)動(dòng)設(shè)計(jì)(model-driven design)。這意味著能夠?qū)⒛P椭械母拍钣成涞皆O(shè)計(jì)/代碼的概念(理想情況下)。模型的變化意味著代碼的變化;更改代碼意味著模型已更改。 DDD并沒(méi)有強(qiáng)制要求您使用面向?qū)ο髞?lái)構(gòu)建域 - 例如,我們可以使用規(guī)則引擎構(gòu)建模型 - 但鑒于主流企業(yè)編程語(yǔ)言是基于OO的,大多數(shù)模型本質(zhì)上都是OO。畢竟,OO基于建模范例。模型的概念將表示為類和接口,作為類成員的職責(zé)。
語(yǔ)言
現(xiàn)在讓我們看一下域驅(qū)動(dòng)設(shè)計(jì)的另一個(gè)基本原則?;仡櫼幌拢何覀兿胍獦?gòu)建一個(gè)捕獲正在構(gòu)建的系統(tǒng)的問(wèn)題域的域模型,并且我們將在代碼/軟件工件中表達(dá)這種理解。為了幫助我們做到這一點(diǎn),DDD提倡領(lǐng)域?qū)<液烷_發(fā)人員有意識(shí)地使用模型中的概念進(jìn)行溝通。因此,域?qū)<也粫?huì)根據(jù)屏幕或菜單項(xiàng)上的字段描述新的用戶故事,而是討論域?qū)ο笏璧幕A(chǔ)屬性或行為。類似地,開發(fā)人員不會(huì)討論數(shù)據(jù)庫(kù)表中的類或列的新實(shí)例變量。
嚴(yán)格要求我們開發(fā)一種普世的語(yǔ)言(ubiquitous language)。如果一個(gè)想法不能輕易表達(dá),那么它表明了一個(gè)概念,這個(gè)概念在領(lǐng)域模型中缺失,并且團(tuán)隊(duì)共同努力找出缺失的概念是什么。一旦建立了這個(gè),那么數(shù)據(jù)庫(kù)表中的屏幕或列上的新字段就會(huì)繼續(xù)顯示。
像DDD一樣,這種開發(fā)無(wú)處不在的語(yǔ)言的想法并不是一個(gè)新想法:XPers稱之為“名稱系統(tǒng)”,多年來(lái)DBA將數(shù)據(jù)字典組合在一起。但無(wú)處不在的語(yǔ)言是一個(gè)令人回味的術(shù)語(yǔ),可以出售給商業(yè)和技術(shù)人員。現(xiàn)在,“整個(gè)團(tuán)隊(duì)”敏捷實(shí)踐正在成為主流,這也很有意義。
模型和上下文......
每當(dāng)我們討論模型時(shí),它總是在某種情況下。通??梢詮氖褂迷撓到y(tǒng)的最終用戶集推斷出該上下文。因此,我們有一個(gè)部署到交易員的前臺(tái)交易系統(tǒng),或超市收銀員使用的銷售點(diǎn)系統(tǒng)。這些用戶以特定方式與模型的概念相關(guān),并且模型的術(shù)語(yǔ)對(duì)這些用戶有意義,但不一定對(duì)該上下文之外的任何其他人有意義。 DDD稱之為有界上下文(BC)。每個(gè)域模型都只存在于一個(gè)BC中,而BC只包含一個(gè)域模型。
我必須承認(rèn),當(dāng)我第一次讀到關(guān)于BC時(shí),我看不出這一點(diǎn):如果BC與域模型同構(gòu),為什么要引入一個(gè)新術(shù)語(yǔ)?如果只有與BC相互作用的最終用戶,則可能不需要這個(gè)術(shù)語(yǔ)。然而,不同的系統(tǒng)(BC)也相互交互,發(fā)送文件,傳遞消息,調(diào)用API等。如果我們知道有兩個(gè)BC相互交互,那么我們知道我們必須注意在一個(gè)概念之間進(jìn)行轉(zhuǎn)換。領(lǐng)域和其他領(lǐng)域。
在模型周圍設(shè)置明確的邊界也意味著我們可以開始討論這些BC之間的關(guān)系。實(shí)際上,DDD確定了BC之間的一整套關(guān)系,因此當(dāng)我們需要將不同的BC鏈接在一起時(shí),我們可以合理地確定應(yīng)該做什么:
已發(fā)布的語(yǔ)言:交互式BCs就共同的語(yǔ)言(例如企業(yè)服務(wù)總線上的一堆XML模式)達(dá)成一致,通過(guò)它們可以相互交互;
開放主機(jī)服務(wù):BC指定任何其他BC可以使用其服務(wù)的協(xié)議(例如RESTful Web服務(wù));
共享內(nèi)核:兩個(gè)BC使用一個(gè)共同的代碼內(nèi)核(例如一個(gè)庫(kù))作為一個(gè)通用的通用語(yǔ)言,但是否則以他們自己的特定方式執(zhí)行其他的東西;
客戶/供應(yīng)商:一個(gè)BC使用另一個(gè)BC的服務(wù),并且是另一個(gè)BC的利益相關(guān)者(客戶)。因此,它可以影響該BC提供的服務(wù);
順從者:一個(gè)BC使用另一個(gè)BC的服務(wù),但不是其他BC的利益相關(guān)者。因此,它使用“原樣”(符合)BC提供的協(xié)議或API;
反腐蝕層:一個(gè)BC使用另一個(gè)服務(wù)而不是利益相關(guān)者,但旨在通過(guò)引入一組適配器 - 一個(gè)反腐敗層來(lái)最小化它所依賴的BC變化的影響。
你可以看到,在這個(gè)列表中,兩個(gè)BC之間的合作水平逐漸降低(見圖2)。使用已發(fā)布的語(yǔ)言(published language),我們從BC建立一個(gè)他們可以互動(dòng)的共同標(biāo)準(zhǔn)開始;既不擁有這種語(yǔ)言,而是由他們所居住的企業(yè)所擁有(甚至可能是行業(yè)標(biāo)準(zhǔn))。有了開放主機(jī)服務(wù)(open host),我們?nèi)匀蛔龅煤芎? BC提供其作為任何其他BC調(diào)用的運(yùn)行時(shí)服務(wù)的功能,但是(可能)隨著服務(wù)的發(fā)展將保持向后兼容性。
Figure 2: Spectrum of Bounded Context Relationship
然而,當(dāng)我們走向順從時(shí),我們只是和我們一起生活; 一個(gè)BC明顯屈服于另一個(gè)。 如果我們必須與購(gòu)買megabucks的總分類帳系統(tǒng)集成,那可能就是我們所處的情況。如果我們使用反腐敗層,那么我們通常會(huì)與遺留系統(tǒng)集成,但是 額外的層將我們盡可能地隔離開來(lái)。 當(dāng)然,這需要花錢來(lái)實(shí)施,但它降低了依賴風(fēng)險(xiǎn)。 反腐敗層也比重新實(shí)現(xiàn)遺留系統(tǒng)便宜很多,這最多會(huì)分散我們對(duì)核心域的注意力,最壞的情況是以失敗告終。
DDD建議我們制定一個(gè)上下文圖(context map t)來(lái)識(shí)別我們的BC以及我們依賴或依賴的BC,以確定這些依賴關(guān)系的性質(zhì)。 圖3顯示了我過(guò)去5年左右一直在研究的系統(tǒng)的上下文映射。
Figure 3: Context Mapping Example
所有這些關(guān)于背景圖和BC的討論有時(shí)被稱為戰(zhàn)略性DDD( strategic DDD),并且有充分的理由。 畢竟,當(dāng)你想到它時(shí),弄清楚BC之間的關(guān)系是非常政治的:我的系統(tǒng)將依賴哪些上游系統(tǒng),我是否容易與它們集成,我是否能夠利用它們,我相信它們嗎? 下游也是如此:哪些系統(tǒng)將使用我的服務(wù),我如何將我的功能作為服務(wù)公開,他們會(huì)對(duì)我有利嗎? 誤解了這一點(diǎn),您的應(yīng)用程序可能很容易失敗。
層和六邊形
現(xiàn)在讓我們轉(zhuǎn)向內(nèi)部并考慮我們自己的BC(系統(tǒng))的架構(gòu)。 從根本上說(shuō),DDD只關(guān)心域?qū)?,?shí)際上,它對(duì)其他層有很多話要說(shuō):表示,應(yīng)用程序或基礎(chǔ)架構(gòu)(或持久層)。 但它確實(shí)期望它們存在。 這是分層架構(gòu)模式(圖4)。
Figure 4: Layered Architecture
當(dāng)然,我們多年來(lái)一直在構(gòu)建多層系統(tǒng),但這并不意味著我們必須擅長(zhǎng)它。確實(shí),過(guò)去的一些主流技術(shù) - 是的,EJB 2,我正在看著你! - 對(duì)域模型可以作為有意義的層存在的想法產(chǎn)生了積極的影響。所有的業(yè)務(wù)邏輯似乎滲透到應(yīng)用層或(更糟糕的)表示層,留下一組貧血的域類[3]作為數(shù)據(jù)持有者的空殼。這不是DDD的意思。
因此,要絕對(duì)清楚,應(yīng)用程序?qū)又胁粦?yīng)存在任何域邏輯。相反,應(yīng)用程序?qū)迂?fù)責(zé)事務(wù)管理和安全性等事務(wù)。在某些體系結(jié)構(gòu)中,它還可能負(fù)責(zé)確保從基礎(chǔ)結(jié)構(gòu)/持久層中檢索的域?qū)ο笤谂c之交互之前已正確初始化(盡管我更喜歡基礎(chǔ)結(jié)構(gòu)層執(zhí)行此操作)。
在表示層在單獨(dú)的存儲(chǔ)空間中運(yùn)行的情況下,應(yīng)用層也充當(dāng)表示層和域?qū)又g的中介。表示層通常處理域?qū)ο蠡蛴驅(qū)ο螅〝?shù)據(jù)傳輸對(duì)象或DTO)的可序列化表示,通常每個(gè)“視圖”一個(gè)。如果這些被修改,那么表示層會(huì)將任何更改發(fā)送回應(yīng)用程序?qū)?,而?yīng)用程序?qū)佑执_定已修改的域?qū)ο螅瑥某志脤蛹虞d它們,然后轉(zhuǎn)發(fā)對(duì)這些域?qū)ο蟮母摹?/p>
分層體系結(jié)構(gòu)的一個(gè)缺點(diǎn)是它建議從表示層一直到基礎(chǔ)結(jié)構(gòu)層的依賴性的線性堆疊。但是,我們可能希望在表示層和基礎(chǔ)結(jié)構(gòu)層中支持不同的實(shí)現(xiàn)。如果(正如我認(rèn)為的那樣?。┪覀兿胍獪y(cè)試我們的應(yīng)用程序就是這種情況:
例如,F(xiàn)itNesse [4]等工具允許我們從最終用戶的角度驗(yàn)證我們系統(tǒng)的行為。但是這些工具通常不會(huì)通過(guò)表示層,而是直接進(jìn)入下一層,即應(yīng)用層。所以從某種意義上說(shuō),F(xiàn)itNesse就是另一種觀察者。
同樣,我們可能有多個(gè)持久性實(shí)現(xiàn)。我們的生產(chǎn)實(shí)現(xiàn)可能使用RDBMS或類似技術(shù),但是對(duì)于測(cè)試和原型設(shè)計(jì),我們可能有一個(gè)輕量級(jí)實(shí)現(xiàn)(甚至可能在內(nèi)存中),因此我們可以模擬持久性。
我們可能還想?yún)^(qū)分“內(nèi)部”和“外部”層之間的交互,其中內(nèi)部我指的是兩個(gè)層完全在我們的系統(tǒng)(或BC)內(nèi)的交互,而外部交互跨越BC。
因此,不要將我們的應(yīng)用程序視為一組圖層,另一種方法是將其視為六邊形[5],如圖5所示。我們的最終用戶使用的查看器以及FitNesse測(cè)試使用內(nèi)部客戶端API(或端口),而來(lái)自其他BC的調(diào)用(例如,RESTful用于開放主機(jī)交互,或來(lái)自ESB適配器的調(diào)用用于已發(fā)布的語(yǔ)言交互)命中外部客戶端端口。對(duì)于后端基礎(chǔ)架構(gòu)層,我們可以看到用于替代對(duì)象存儲(chǔ)實(shí)現(xiàn)的持久性端口,此外,域?qū)又械膶?duì)象可以通過(guò)外部服務(wù)端口調(diào)用其他BC。
Figure 5: Hexagonal Architecture
但這足夠大的東西; 讓我們來(lái)看看DDD在煤炭面板上的樣子。
構(gòu)建模塊
正如我們已經(jīng)注意到的,大多數(shù)DDD系統(tǒng)可能會(huì)使用OO范例。因此,我們的域?qū)ο蟮脑S多構(gòu)建塊可能很熟悉,例如實(shí)體,值對(duì)象和模塊(entities, value objects and modules. )。例如,如果您是Java程序員,那么將DDD實(shí)體視為與JPA實(shí)體基本相同(使用@Entity注釋)就足夠安全了;值對(duì)象是字符串,數(shù)字和日期之類的東西;一個(gè)模塊就是一個(gè)包。
但是,DDD傾向于更多地強(qiáng)調(diào)值對(duì)象(value objects ),而不是過(guò)去習(xí)慣。所以,是的,您可以使用String來(lái)保存Customer的givenName屬性的值,例如,這可能是合理的。但是一筆錢,例如產(chǎn)品的價(jià)格呢?我們可以使用int或double,但是(甚至忽略可能的舍入錯(cuò)誤)1或1.0是什么意思? $ 1嗎? 1? ¥1? 1分,甚至?相反,我們應(yīng)該引入一個(gè)Money值類型,它封裝了Currency和任何舍入規(guī)則(將特定于Currency)。
而且,值對(duì)象應(yīng)該是不可變的,并且應(yīng)該提供一組無(wú)副作用的函數(shù)來(lái)操作它們。我們應(yīng)該寫:
Money m1=new Money("GBP", 10);
Money m2=new Money("GBP", 20);
Money m3=m1.add(m2);
將m2添加到m1不會(huì)改變m1,而是返回一個(gè)新的Money對(duì)象(由m3引用),它表示一起添加的兩個(gè)Money。
值也應(yīng)該具有值語(yǔ)義,這意味著(例如在Java和C#中)它們實(shí)現(xiàn)equals()和hashCode()。它們通常也可以序列化,可以是字節(jié)流,也可以是String格式。當(dāng)我們需要堅(jiān)持它們時(shí),這很有用。
值對(duì)象常見的另一種情況是標(biāo)識(shí)符。因此,(US)SocialSecurityNumber將是一個(gè)很好的例子,車輛的RegistrationNumber也是如此。 URL也是如此。因?yàn)槲覀円呀?jīng)重寫了equals()和hashCode(),所以這些都可以安全地用作哈希映射中的鍵。
引入價(jià)值對(duì)象不僅擴(kuò)展了我們無(wú)處不在的語(yǔ)言,還意味著我們可以將行為推向價(jià)值觀本身。因此,如果我們確定Money永遠(yuǎn)不會(huì)包含負(fù)值,我們可以在Money內(nèi)部實(shí)現(xiàn)此檢查,而不是在使用Money的任何地方。如果SocialSecurityNumber具有校驗(yàn)和數(shù)字(在某些國(guó)家/地區(qū)就是這種情況),則該校驗(yàn)和的驗(yàn)證可以在值對(duì)象中。我們可以要求URL驗(yàn)證其格式,返回其方案(例如http),或者確定相對(duì)于其他URL的資源位置。
我們的另外兩個(gè)構(gòu)建塊可能需要更少的解釋。實(shí)體通常是持久的,通常是可變的并且(因此)傾向于具有一生的狀態(tài)變化。在許多體系結(jié)構(gòu)中,實(shí)體將作為行保存在數(shù)據(jù)庫(kù)表中。同時(shí),模塊(包或命名空間)是確保域模型保持解耦的關(guān)鍵,并且不會(huì)成為泥漿中的一大塊[6]。在他的書中,埃文斯談到概念輪廓,這是一個(gè)優(yōu)雅的短語(yǔ),用于描述如何區(qū)分域的主要關(guān)注領(lǐng)域。模塊是實(shí)現(xiàn)這種分離的主要方式,以及確保模塊依賴性嚴(yán)格非循環(huán)的接口。我們使用諸如Uncle“Bob”Martin的依賴倒置原則[7]之類的技術(shù)來(lái)確保依賴關(guān)系是嚴(yán)格單向的。
實(shí)體,值和模塊是核心構(gòu)建塊,但DDD還有一些不太熟悉的構(gòu)建塊。我們現(xiàn)在來(lái)看看這些。
聚合和聚合根
如果您精通UML,那么您將記住,它允許我們將兩個(gè)對(duì)象之間的關(guān)聯(lián)建模為簡(jiǎn)單關(guān)聯(lián),聚合或使用組合。聚合根(有時(shí)縮寫為AR)是通過(guò)組合組成其他實(shí)體(以及它自己的值)的實(shí)體。也就是說(shuō),聚合實(shí)體僅由根引用(可能是可傳遞的),并且可能不會(huì)被聚合外的任何對(duì)象(永久地)引用。換句話說(shuō),如果實(shí)體具有對(duì)另一個(gè)實(shí)體的引用,則引用的實(shí)體必須位于同一聚合內(nèi),或者是某個(gè)其他聚合的根。
許多實(shí)體是聚合根,不包含其他實(shí)體。對(duì)于不可變的實(shí)體(相當(dāng)于數(shù)據(jù)庫(kù)中的引用或靜態(tài)數(shù)據(jù))尤其如此。示例可能包括Country,VehicleModel,TaxRate,Category,BookTitle等。
但是,更復(fù)雜的可變(事務(wù))實(shí)體在建模為聚合時(shí)確實(shí)會(huì)受益,主要是通過(guò)減少概念開銷。我們不必考慮每個(gè)實(shí)體,而只考慮聚合根;聚合實(shí)體僅僅是聚合的“內(nèi)部運(yùn)作”。它們還簡(jiǎn)化了實(shí)體之間的相互作用;我們遵循以下規(guī)則:(持久化)引用可能只是聚合的根,而不是聚合中的任何其他實(shí)體。
另一個(gè)DDD原則是聚合根負(fù)責(zé)確保聚合實(shí)體始終處于有效狀態(tài)。例如,Order(root)可能包含OrderItems的集合(聚合)。可能存在以下規(guī)則:訂單發(fā)貨后,任何OrderItem都無(wú)法更新。或者,如果兩個(gè)OrderItem引用相同的產(chǎn)品并具有相同的運(yùn)輸要求,則它們將合并到同一個(gè)OrderItem中?;蛘撸琌rder的派生totalPrice屬性應(yīng)該是OrderItems的價(jià)格總和。維護(hù)這些不變量是root的責(zé)任。
但是......只有聚合根才能完全在聚合中維護(hù)對(duì)象之間的不變量。 OrderItem引用的產(chǎn)品幾乎肯定不會(huì)在AR中,因?yàn)檫€有其他用例需要與Product進(jìn)行交互,而不管是否有訂單。因此,如果有一條規(guī)則不能對(duì)已停產(chǎn)的產(chǎn)品下達(dá)訂單,那么訂單將需要以某種方式處理。實(shí)際上,這通常意味著在訂單交易更新時(shí)使用隔離級(jí)別2或3來(lái)“鎖定”產(chǎn)品?;蛘?,可以使用帶外過(guò)程來(lái)協(xié)調(diào)交叉聚合不變量的任何破壞。
在我們繼續(xù)前進(jìn)之前退一步,我們可以看到我們有一系列粒度:
value < entity < aggregate < module < bounded context
現(xiàn)在讓我們繼續(xù)研究一些DDD構(gòu)建塊。
存儲(chǔ)庫(kù),工廠和服務(wù)(Repositories, Factories and Services)
在企業(yè)應(yīng)用程序中,實(shí)體通常是持久的,其值表示這些實(shí)體的狀態(tài)。但是,我們?nèi)绾螐某志眯源鎯?chǔ)中獲取實(shí)體呢?
存儲(chǔ)庫(kù)是持久性存儲(chǔ)的抽象,返回實(shí)體 - 或者更確切地說(shuō)是聚合根 - 滿足某些標(biāo)準(zhǔn)。例如,客戶存儲(chǔ)庫(kù)將返回Customer聚合根實(shí)體,訂單存儲(chǔ)庫(kù)將返回Orders(及其OrderItems)。通常,每個(gè)聚合根有一個(gè)存儲(chǔ)庫(kù)。
因?yàn)槲覀兺ǔOMС殖志眯源鎯?chǔ)的多個(gè)實(shí)現(xiàn),所以存儲(chǔ)庫(kù)通常由具有不同持久性存儲(chǔ)實(shí)現(xiàn)的不同實(shí)現(xiàn)的接口(例如,CustomerRepository)組成(例如,CustomerRepositoryHibernate或CustomerRepositoryInMemory)。由于此接口返回實(shí)體(域?qū)拥囊徊糠郑虼私涌诒旧硪彩怯驅(qū)拥囊徊糠?。接口的?shí)現(xiàn)(與一些特定的持久性實(shí)現(xiàn)耦合)是基礎(chǔ)結(jié)構(gòu)層的一部分。
我們搜索的標(biāo)準(zhǔn)通常隱含在名為的方法名稱中。因此,CustomerRepository可能會(huì)提供findByLastName(String)方法來(lái)返回具有指定姓氏的Customer實(shí)體?;蛘呶覀兛梢宰孫rderRepository返回Orders,findByOrderNum(OrderNum)返回與OrderNum匹配的Order(請(qǐng)注意,這里使用值類型?。?。
更復(fù)雜的設(shè)計(jì)將標(biāo)準(zhǔn)包裝到查詢或規(guī)范中,類似于findBy(Query <T>),其中Query包含描述標(biāo)準(zhǔn)的抽象語(yǔ)法樹。然后,不同的實(shí)現(xiàn)解包查詢以確定如何以他們自己的特定方式定位滿足條件的實(shí)體。
也就是說(shuō),如果你是.NET開發(fā)人員,那么值得一提的是LINQ [8]。因?yàn)長(zhǎng)INQ本身是可插拔的,所以我們通常可以使用LINQ編寫存儲(chǔ)庫(kù)的單個(gè)實(shí)現(xiàn)。然后變化的不是存儲(chǔ)庫(kù)實(shí)現(xiàn),而是我們配置LINQ以獲取其數(shù)據(jù)源的方式(例如,針對(duì)Entity Framework或針對(duì)內(nèi)存中的對(duì)象庫(kù))。
每個(gè)聚合根使用特定存儲(chǔ)庫(kù)接口的變體是使用通用存儲(chǔ)庫(kù),例如Repository <Customer>。這提供了一組通用方法,例如每個(gè)實(shí)體的findById(int)。當(dāng)使用Query <T>(例如Query <Customer>)對(duì)象指定條件時(shí),這很有效。對(duì)于Java平臺(tái),還有一些框架,例如Hades [9],允許混合和匹配方法(從通用實(shí)現(xiàn)開始,然后在需要時(shí)添加自定義接口)。
存儲(chǔ)庫(kù)不是從持久層引入對(duì)象的唯一方法。如果使用對(duì)象關(guān)系映射(ORM)工具(如Hibernate),我們可以在實(shí)體之間導(dǎo)航引用,允許我們透明地遍歷圖形。根據(jù)經(jīng)驗(yàn),對(duì)其他實(shí)體的聚合根的引用應(yīng)該是延遲加載的,而聚合中的聚合實(shí)體應(yīng)該被急切加載。但與ORM一樣,期望進(jìn)行一些調(diào)整,以便為最關(guān)鍵的用例獲得合適的性能特征。
在大多數(shù)設(shè)計(jì)中,存儲(chǔ)庫(kù)還用于保存新實(shí)例,以及更新或刪除現(xiàn)有實(shí)例。如果底層持久性技術(shù)支持它,那么它們很可能存在于通用存儲(chǔ)庫(kù)中,但是從方法簽名的角度來(lái)看,沒(méi)有什么可以區(qū)分保存新客戶和保存新訂單。
最后一點(diǎn)......直接創(chuàng)建新的聚合根很少見。相反,它們傾向于由其他聚合根創(chuàng)建。訂單就是一個(gè)很好的例子:它可能是通過(guò)客戶調(diào)用一個(gè)動(dòng)作來(lái)創(chuàng)建的。
這整齊地帶給我們:
工廠
如果我們要求Order創(chuàng)建一個(gè)OrderItem,那么(因?yàn)楫吘筄rderItem是其聚合的一部分),Order知道要實(shí)例化的具體OrderItem類是合理的。實(shí)際上,實(shí)體知道它需要實(shí)例化的同一模塊(命名空間或包)中的任何實(shí)體的具體類是合理的。
假設(shè)客戶使用Customer的placeOrder操作創(chuàng)建訂單(參見圖6)。如果客戶知道具體的訂單類,則意味著客戶模塊依賴于訂單模塊。如果訂單具有對(duì)客戶的反向引用,那么我們將在兩個(gè)模塊之間獲得循環(huán)依賴。
Figure 6: Customers and Orders (cyclic dependencie
如前所述,我們可以使用依賴性反轉(zhuǎn)原則來(lái)解決這類問(wèn)題:從訂單中刪除依賴關(guān)系 - >客戶模塊我們將引入OrderOwner接口,使Order引用為OrderOwner,并使Customer實(shí)現(xiàn)OrderOwner(參見圖7))。
Figure 7: Customers and Orders (customer depends o
那么另一種方式呢:如果我們想要訂單 - >客戶? 在這種情況下,需要在客戶模塊中有一個(gè)表示Order的接口(這是Customer的placeOrder操作的返回類型)。 然后,訂單模塊將提供訂單的實(shí)現(xiàn)。 由于客戶不能依賴訂單,因此必須定義OrderFactory接口。 然后,訂單模塊依次提供OrderFactory的實(shí)現(xiàn)(參見圖8)。
可能還有相應(yīng)的存儲(chǔ)庫(kù)接口。例如,如果客戶可能有數(shù)千個(gè)訂單,那么我們可能會(huì)刪除其訂單集合。相反,客戶將使用OrderRepository根據(jù)需要定位其訂單(的一部分)?;蛘撸ㄈ缒承┤怂福?,您可以通過(guò)將對(duì)存儲(chǔ)庫(kù)的調(diào)用移動(dòng)到應(yīng)用程序體系結(jié)構(gòu)的更高層(例如域服務(wù)或應(yīng)用程序服務(wù))來(lái)避免從實(shí)體到存儲(chǔ)庫(kù)的顯式依賴性。
實(shí)際上,服務(wù)是我們需要探索的下一個(gè)話題。
域服務(wù),基礎(chǔ)結(jié)構(gòu)服務(wù)和應(yīng)用程序服務(wù)(Domain services, Infrastructure services and Application services)
域服務(wù)(domain service)是在域?qū)觾?nèi)定義的域服務(wù),但實(shí)現(xiàn)可以是基礎(chǔ)結(jié)構(gòu)層的一部分。存儲(chǔ)庫(kù)是域服務(wù),其實(shí)現(xiàn)確實(shí)在基礎(chǔ)結(jié)構(gòu)層中,而工廠也是域服務(wù),其實(shí)現(xiàn)通常在域?qū)觾?nèi)。特別是在適當(dāng)?shù)哪K中定義了存儲(chǔ)庫(kù)和工廠:CustomerRepository位于客戶模塊中,依此類推。
更一般地說(shuō),域服務(wù)是任何不容易在實(shí)體中生存的業(yè)務(wù)邏輯。埃文斯建議在兩個(gè)銀行賬戶之間進(jìn)行轉(zhuǎn)賬服務(wù),但我不確定這是最好的例子(我會(huì)將轉(zhuǎn)賬本身建模為一個(gè)實(shí)體)。但另一種域服務(wù)是一種充當(dāng)其他有界上下文的代理。例如,我們可能希望與暴露開放主機(jī)服務(wù)的General Ledger系統(tǒng)集成。我們可以定義一個(gè)公開我們需要的功能的服務(wù),以便我們的應(yīng)用程序可以將條目發(fā)布到總帳。這些服務(wù)有時(shí)會(huì)定義自己的實(shí)體,這些實(shí)體可能會(huì)持久化;這些實(shí)體實(shí)際上影響了在另一個(gè)BC中遠(yuǎn)程保存的顯著信息。
我們還可以獲得技術(shù)性更強(qiáng)的服務(wù),例如發(fā)送電子郵件或SMS文本消息,或?qū)orrespondence實(shí)體轉(zhuǎn)換為PDF,或使用條形碼標(biāo)記生成的PDF。接口在域?qū)又卸x,但實(shí)現(xiàn)在基礎(chǔ)架構(gòu)層中非常明確。因?yàn)檫@些非常技術(shù)性服務(wù)的接口通常是根據(jù)簡(jiǎn)單的值類型(而不是實(shí)體)來(lái)定義的,所以我傾向于使用術(shù)語(yǔ)基礎(chǔ)結(jié)構(gòu)服務(wù)(infrastructure service)而不是域服務(wù)。但是如果你想成為一個(gè)“電子郵件”BC或“SMS”BC的橋梁,你可以想到它們。
雖然域服務(wù)既可以調(diào)用域?qū)嶓w也可以調(diào)用域?qū)嶓w,但應(yīng)用服務(wù)(application service)位于域?qū)又希虼擞驅(qū)觾?nèi)的實(shí)體不能調(diào)用,只能反過(guò)來(lái)調(diào)用。換句話說(shuō),應(yīng)用層(我們的分層架構(gòu))可以被認(rèn)為是一組(無(wú)狀態(tài))應(yīng)用服務(wù)。
如前所述,應(yīng)用程序服務(wù)通常處理交叉和安全等交叉問(wèn)題。他們還可以通過(guò)以下方式與表示層進(jìn)行調(diào)解:解組入站請(qǐng)求;使用域服務(wù)(存儲(chǔ)庫(kù)或工廠)獲取對(duì)與之交互的聚合根的引用;在該聚合根上調(diào)用適當(dāng)?shù)牟僮?并將結(jié)果編組回表示層。
我還應(yīng)該指出,在某些體系結(jié)構(gòu)中,應(yīng)用程序服務(wù)調(diào)用基礎(chǔ)結(jié)構(gòu)服務(wù)。因此,應(yīng)用服務(wù)可以直接調(diào)用PdfGenerationService,傳遞從實(shí)體中提取的信息,而不是實(shí)體調(diào)用PdfGenerationService將其自身轉(zhuǎn)換為PDF。這不是我的特別偏好,但它是一種常見的設(shè)計(jì)。我很快就會(huì)談到這一點(diǎn)。
好的,這完成了我們對(duì)主要DDD模式的概述。在Evans 500 +頁(yè)面書中還有更多內(nèi)容 - 值得一讀 - 但我接下來(lái)要做的是突出顯示人們似乎很難應(yīng)用DDD的一些領(lǐng)域。
問(wèn)題和障礙
實(shí)施分層架構(gòu)
這是第一件事:嚴(yán)格執(zhí)行架構(gòu)分層可能很困難。特別是,從域?qū)拥綉?yīng)用層的業(yè)務(wù)邏輯滲透可能特別隱蔽。
我已經(jīng)在這里挑出了Java的EJB2作為罪魁禍?zhǔn)?,但是模?- 視圖 - 控制器模式的不良實(shí)現(xiàn)也可能導(dǎo)致這種情況發(fā)生??刂破鳎?應(yīng)用層)會(huì)發(fā)生什么,承擔(dān)太多責(zé)任,讓模型(=域?qū)樱┳兊秘氀J聦?shí)上,有更新的Web框架(在Java世界中,Wicket [10]是一個(gè)嶄露頭角的例子),出于這種原因明確地避免了MVC模式。
表示層模糊了域?qū)?/p>
另一個(gè)問(wèn)題是嘗試開發(fā)無(wú)處不在的語(yǔ)言。領(lǐng)域?qū)<以谄聊环矫嬲勗捠呛茏匀坏模驗(yàn)楫吘?,這就是他們可以看到的系統(tǒng)。要求他們?cè)谄聊缓竺娌榭床⒃谟蚋拍罘矫姹磉_(dá)他們的問(wèn)題可能非常困難。
表示層本身也可能存在問(wèn)題,因?yàn)樽远x表示層可能無(wú)法準(zhǔn)確反映(可能會(huì)扭曲)底層域概念,從而破壞我們無(wú)處不在的語(yǔ)言。即使不是這種情況,也只需要將用戶界面組合在一起所需的時(shí)間。使用敏捷術(shù)語(yǔ),速度降低意味著每次迭代的進(jìn)度較少,因此對(duì)整個(gè)域的深入了解較少。
存儲(chǔ)庫(kù)模式的實(shí)現(xiàn)
從更技術(shù)性的角度來(lái)看,新手有時(shí)似乎也會(huì)混淆將存儲(chǔ)庫(kù)(在域?qū)又校┡c其實(shí)現(xiàn)(在基礎(chǔ)架構(gòu)層中)的接口分離出來(lái)。我不確定為什么會(huì)這樣:畢竟,這是一個(gè)非常簡(jiǎn)單的OO模式。我想這可能是因?yàn)榘N乃沟臅](méi)有達(dá)到這個(gè)細(xì)節(jié)水平,這讓一些人變得高高在上。但這也可能是因?yàn)樘鎿Q持久性實(shí)現(xiàn)(根據(jù)六邊形體系結(jié)構(gòu))的想法并不普遍,導(dǎo)致持久性實(shí)現(xiàn)滲透到域?qū)拥南到y(tǒng)。
服務(wù)依賴項(xiàng)的實(shí)現(xiàn)
另一個(gè)技術(shù)問(wèn)題 - 在DDD從業(yè)者之間可能存在分歧 - 就實(shí)體與域/基礎(chǔ)設(shè)施服務(wù)(包括存儲(chǔ)庫(kù)和工廠)之間的關(guān)系而言。有些人認(rèn)為實(shí)體根本不應(yīng)該依賴域服務(wù),但如果是這種情況,則外部應(yīng)用程序服務(wù)與域服務(wù)交互并將結(jié)果傳遞給域?qū)嶓w。根據(jù)我的思維方式,這使我們走向了一個(gè)貧血的領(lǐng)域模型。
稍微柔和的觀點(diǎn)是實(shí)體可以依賴于域服務(wù),但應(yīng)用程序服務(wù)應(yīng)該根據(jù)需要傳遞它們,例如作為操作的參數(shù)。我也不喜歡這個(gè):對(duì)我而言,它將實(shí)現(xiàn)細(xì)節(jié)暴露給應(yīng)用層(“這個(gè)實(shí)體需要這樣一個(gè)服務(wù)才能完成這個(gè)操作”)。但是許多從業(yè)者對(duì)這種方法感到滿意。
我自己的首選方案是使用依賴注入將服務(wù)注入實(shí)體。實(shí)體可以聲明它們的依賴關(guān)系,然后基礎(chǔ)結(jié)構(gòu)層(例如Hibernate,Spring或其他一些框架)可以將服務(wù)注入實(shí)體:
public class Customer {
…
private OrderFactory orderFactory;
public void setOrderFactory(OrderFactory orderFactory) {
this.orderFactory=orderFactory;
}
…
public Order placeOrder( … ) {
Order order=orderFactory.createOrder();
…
return order;
}
}
一種替代方法是使用服務(wù)定位器模式。例如,將所有服務(wù)注冊(cè)到JNDI中,然后每個(gè)域?qū)ο蟛檎宜璧姆?wù)。在我看來(lái),這引入了對(duì)運(yùn)行時(shí)環(huán)境的依賴。但是,與依賴注入相比,它對(duì)實(shí)體的內(nèi)存需求較低,這可能是一個(gè)決定性因素。
不合適的模塊化
正如我們已經(jīng)確定的那樣,DDD在實(shí)體之上區(qū)分了幾種不同的粒度級(jí)別,即聚合,模塊和BC。獲得正確的模塊化水平需要一些練習(xí)。正如RDBMS模式可能被非規(guī)范化一樣,系統(tǒng)也沒(méi)有模塊化(成為泥漿的大球)。但是,過(guò)度規(guī)范化的RDBMS模式(其中單個(gè)實(shí)體在多個(gè)表上被分解)也可能是有害的,過(guò)模塊化系統(tǒng)也是如此,因?yàn)樗兊秒y以理解系統(tǒng)如何作為整體工作。
我們首先考慮模塊和BC。記住,模塊類似于Java包或.NET命名空間。我們希望兩個(gè)模塊之間的依賴關(guān)系是非循環(huán)的,但是如果我們確定(比如說(shuō))客戶依賴于訂單,那么我們不需要做任何額外的事情:客戶可以簡(jiǎn)單地導(dǎo)入Order包/命名空間并使用它接口和類根據(jù)需要。
但是,如果我們將客戶和訂單放入單獨(dú)的BC中,那么我們還有更多的工作要做,因?yàn)槲覀儽仨殞⒖蛻鬊C中的概念映射到BC訂單的概念。在實(shí)踐中,這還意味著在客戶BC中具有訂單實(shí)體的表示(根據(jù)前面給出的總分類帳示例),以及通過(guò)消息總線或其他東西實(shí)際協(xié)作的機(jī)制。請(qǐng)記住:擁有兩個(gè)BC的原因是當(dāng)有不同的最終用戶和/或利益相關(guān)者時(shí),我們無(wú)法保證不同BC中的相關(guān)概念將朝著相同的方向發(fā)展。
另一個(gè)可能存在混淆的領(lǐng)域是將實(shí)體與聚合區(qū)分開來(lái)。每個(gè)聚合都有一個(gè)實(shí)體作為其聚合根,對(duì)于很多很多實(shí)體,聚合將只包含這個(gè)實(shí)體(“瑣碎”的情況,正如數(shù)學(xué)家所說(shuō)的那樣)。但我看到開發(fā)人員認(rèn)為整個(gè)世界必須存在于一個(gè)聚合中。因此,例如,訂單包含引用產(chǎn)品的OrderItems(到目前為止一直很好),因此開發(fā)人員得出結(jié)論,產(chǎn)品也在聚合中(不?。└愀獾氖?,開發(fā)人員會(huì)觀察到客戶有訂單,所以想想這個(gè)意味著我們必須擁有Customer / Order / OrderItem / Product的巨型聚合(不,不,不?。?。關(guān)鍵是“客戶有訂單”并不意味著暗示匯總;客戶,訂單和產(chǎn)品都是集合的根源。
實(shí)際上,一個(gè)典型的模塊(這是非常粗糙和準(zhǔn)備好的)可能包含六個(gè)聚合,每個(gè)聚合可能包含一個(gè)實(shí)體和幾個(gè)實(shí)體之間。在這六個(gè)中,一個(gè)好的數(shù)字可能是不可變的“參考數(shù)據(jù)”類。還要記住,我們模塊化的原因是我們可以理解一件事(在一定的粒度級(jí)別)。所以要記住,典型的人一次只能保持在5到9個(gè)之間[11]。
入門
正如我在開始時(shí)所說(shuō),你可能在DDD之前遇到過(guò)很多想法。事實(shí)上,我所說(shuō)過(guò)的每一個(gè)Smalltalker(我不是一個(gè),我不敢說(shuō))似乎很高興能夠在EJB2等人的荒野歲月之后回歸域驅(qū)動(dòng)的方法。
另一方面,如果這些東西是新的怎么辦?有這么多不同的方式來(lái)絆倒,有沒(méi)有辦法可靠地開始使用DDD?
如果你環(huán)顧一下Java領(lǐng)域(對(duì).NET來(lái)說(shuō)并不那么糟糕),實(shí)際上有數(shù)百個(gè)用于構(gòu)建Web應(yīng)用程序的框架(JSP,Struts,JSF,Spring MVC,Seam,Wicket,Tapestry等)。從持久性角度(JDO,JPA,Hibernate,iBatis,TopLink,JCloud等)或其他問(wèn)題(RestEasy,Camel,ServiceMix,Mule等),有很多針對(duì)基礎(chǔ)架構(gòu)層的框架。但是很少有框架或工具來(lái)幫助DDD所說(shuō)的最重要的層,即域?qū)印?/p>
自2002年以來(lái),我一直參與(現(xiàn)在是一個(gè)提交者)一個(gè)名為Naked Objects的項(xiàng)目,Java上的開源[12]和.NET上的商業(yè)[13]。雖然Naked Objects沒(méi)有明確地開始考慮領(lǐng)域驅(qū)動(dòng)的設(shè)計(jì) - 事實(shí)上它早于Evans的書 - 它與DDD的原理非常相似。它還可以輕松克服前面提到的障礙。
您可以將Naked Objects視為與Hibernate等ORM類似。 ORM構(gòu)建域?qū)ο蟮脑P筒⑹褂盟鼇?lái)自動(dòng)將域?qū)ο蟪志帽4娴絉DBMS,而Naked Objects構(gòu)建元模型并使用它在面向?qū)ο蟮挠脩艚缑嬷凶詣?dòng)呈現(xiàn)這些域?qū)ο蟆?/p>
開箱即用的Naked Objects支持兩個(gè)用戶界面,一個(gè)富客戶端查看器(參見圖9)和一個(gè)HTML查看器(參見圖10)。這些都是功能完備的應(yīng)用程序,需要開發(fā)人員只編寫要運(yùn)行的域?qū)樱▽?shí)體,值,存儲(chǔ)庫(kù),工廠,服務(wù))。
Figure 9: Naked Objects Drag-n-Drop Viewer
我們來(lái)看看Claim類的(Java)代碼(如屏幕截圖所示)。首先,這些類基本上是pojos,盡管我們通常從便捷類AbstractDomainObject繼承,只是為了分解注入通用存儲(chǔ)庫(kù)并提供一些幫助方法:
public class Claim extends AbstractDomainObject {
...
}
Next, we have some value properties:
// {{ Description
private String description;
@MemberOrder(sequence="1")
public String getDescription() { return description; }
public void setDescription(String d) { description=d; }
// }}
// {{ Dateprivate Date date;@MemberOrder(sequence="2")public Date getDate() { return date; }public void setDate(Date d) { date=d; }// }}
// {{ Statusprivate String status;@Disabled@MemberOrder(sequence="3")public String getStatus() { return status; }public void setStatus(String s) { status=s; }// }}
這些是簡(jiǎn)單的getter / setter,返回類型為String,日期,整數(shù)等(盡管Naked Objects也支持自定義值類型)。接下來(lái),我們有一些參考屬性:
// {{ Claimant
private Claimant claimant;
@Disabled
@MemberOrder(sequence="4")
public Claimant getClaimant() { return claimant; }
public void setClaimant(Claimant c) { claimant=c; }
// }}
// {{ Approverprivate Approver approver;@Disabled@MemberOrder(sequence="5")public Approver getApprover() { return approver; }public void setApprover(Approver a) { approver=a; }// }}
這里我們的Claim實(shí)體引用其他實(shí)體。實(shí)際上,Claimant和Approver是接口,因此這允許我們將域模型分解為模塊,如前所述。
實(shí)體也可以擁有實(shí)體集合。在我們的案例中,Claim有一個(gè)ClaimItems的集合:
// {{ Items
private List<ClaimItem> items=new
ArrayList<ClaimItem>();
@MemberOrder(sequence="6")
public List<ClaimItem> getItems() { return items; }
public void addToItems(ClaimItem item) {
items.add(item);
}
// }}
我們還有(Naked Objects調(diào)用的)動(dòng)作,即submit和addItem:這些都是不代表屬性和集合的公共方法:
// {{ action: addItem
public void addItem(
@Named("Days since")
int days,
@Named("Amount")
double amount,
@Named("Description")
String description) {
ClaimItem claimItem=newTransientInstance(ClaimItem.class);
Date date=new Date();
date=date.add(0,0, days);
claimItem.setDateIncurred(date);
claimItem.setDescription(description);
claimItem.setAmount(new Money(amount, "USD"));
persist(claimItem);
addToItems(claimItem);
}
public String disableAddItem() {
return "Submitted".equals(getStatus()) ? "Already
submitted" : null;
}
// }}
// {{ action: Submit
public void submit(Approver approver) {
setStatus("Submitted");
setApprover(approver);
}
public String disableSubmit() {
return getStatus().equals("New")?
null : "Claim has already been submitted";
}
public Object[] defaultSubmit() {
return new Object[] { getClaimant().getApprover() };
}
// }}
這些操作會(huì)在Naked Objects查看器中自動(dòng)呈現(xiàn)為菜單項(xiàng)或鏈接。而這些行動(dòng)的存在意味著Naked Objects應(yīng)用程序不僅僅是CRUD風(fēng)格的應(yīng)用程序。
最后,有一些支持方法可以顯示標(biāo)簽(或標(biāo)題)并掛鉤持久性生命周期:
// {{ Title
public String title() {
return getStatus() + " - " + getDate();
}
// }}
// {{ Lifecyclepublic void created() {status="New";date=new Date();}// }}
之前我將Naked Objects域?qū)ο竺枋鰹閜ojos,但您會(huì)注意到我們使用注釋(例如@Disabled)以及命令式幫助器方法(例如disableSubmit())來(lái)強(qiáng)制執(zhí)行業(yè)務(wù)約束。 Naked Objects查看器通過(guò)查詢啟動(dòng)時(shí)構(gòu)建的元模型來(lái)尊重這些語(yǔ)義。如果您不喜歡這些編程約定,則可以更改它們。
典型的Naked Objects應(yīng)用程序由一組域類組成,例如上面的Claim類,以及存儲(chǔ)庫(kù),工廠和域/基礎(chǔ)結(jié)構(gòu)服務(wù)的接口和實(shí)現(xiàn)。特別是,沒(méi)有表示層或應(yīng)用層代碼。那么Naked Objects如何幫助解決我們已經(jīng)確定的一些障礙?
實(shí)施分層架構(gòu):因?yàn)槲覀兙帉懙奈ㄒ淮a是域?qū)ο螅蜻壿嫙o(wú)法滲透到其他層。實(shí)際上,Naked Objects最初的動(dòng)機(jī)之一就是幫助開發(fā)行為完整的對(duì)象
表示層模糊了域?qū)樱阂驗(yàn)楸硎緦邮怯驅(qū)ο蟮闹苯臃从?,整個(gè)團(tuán)隊(duì)可以迅速加深對(duì)域模型的理解。默認(rèn)情況下,Naked Objects直接從代碼中獲取類名和方法名,因此強(qiáng)烈要求在無(wú)處不在的語(yǔ)言中獲得命名權(quán)。通過(guò)這種方式,Naked Objects也支持DDD的模型驅(qū)動(dòng)設(shè)計(jì)原理
存儲(chǔ)庫(kù)模式的實(shí)現(xiàn):您可以在屏幕截圖中看到的圖標(biāo)/鏈接實(shí)際上是存儲(chǔ)庫(kù):EmployeeRepository和ClaimRepository。 Naked Objects支持可插入對(duì)象存儲(chǔ),通常在原型設(shè)計(jì)中,我們使用針對(duì)內(nèi)存中對(duì)象存儲(chǔ)的實(shí)現(xiàn)。當(dāng)我們轉(zhuǎn)向生產(chǎn)時(shí),我們會(huì)編寫一個(gè)實(shí)現(xiàn)數(shù)據(jù)庫(kù)的實(shí)現(xiàn)。
服務(wù)依賴項(xiàng)的實(shí)現(xiàn):Naked Objects會(huì)自動(dòng)將服務(wù)依賴項(xiàng)注入每個(gè)域?qū)ο?。這是在從對(duì)象庫(kù)中檢索對(duì)象時(shí),或者首次創(chuàng)建對(duì)象時(shí)完成的(請(qǐng)參閱上面的newTransientInstance())。事實(shí)上,這些輔助方法所做的就是委托Naked Objects提供的名為DomainObjectContainer的通用存儲(chǔ)庫(kù)/工廠。
不合適的模塊化:我們可以通過(guò)正常方式使用Java包(或.NET命名空間)模塊化為模塊,并使用Structure101 [14]和NDepend [15]等可視化工具來(lái)確保我們的代碼庫(kù)中沒(méi)有循環(huán)依賴。我們可以通過(guò)注釋@Hidden來(lái)模塊化為聚合,任何聚合對(duì)象代表我們可見聚合根的內(nèi)部工作;這些將不會(huì)出現(xiàn)在Naked Objects查看器中。我們可以編寫域和基礎(chǔ)設(shè)施服務(wù),以便根據(jù)需要橋接到其他BC。
Naked Objects提供了許多其他功能:它具有可擴(kuò)展的體系結(jié)構(gòu) - 特別是 - 允許實(shí)現(xiàn)其他查看器和對(duì)象存儲(chǔ)。正在開發(fā)的下一代觀眾(例如Scimpi [16])提供更復(fù)雜的定制功能。此外,它還提供多種部署選項(xiàng):例如,您可以使用Naked Objects進(jìn)行原型設(shè)計(jì),然后在進(jìn)行生產(chǎn)時(shí)開發(fā)自己的定制表示層。它還與FitNesse [17]等工具集成,可以自動(dòng)為域?qū)ο筇峁㏑ESTful接口[18]。
下一步
領(lǐng)域驅(qū)動(dòng)的設(shè)計(jì)匯集了一組用于開發(fā)復(fù)雜企業(yè)應(yīng)用程序的最佳實(shí)踐模式。一些開發(fā)人員多年來(lái)一直在應(yīng)用這些模式,對(duì)于這些人來(lái)說(shuō),DDD可能只是對(duì)他們現(xiàn)有實(shí)踐的肯定。但對(duì)于其他人來(lái)說(shuō),應(yīng)用這些模式可能是一個(gè)真正的挑戰(zhàn)。
Naked Objects為Java和.NET提供了一個(gè)框架,通過(guò)處理其他層,團(tuán)隊(duì)可以專注于重要的部分,即域模型。通過(guò)直接在UI中公開域?qū)ο螅琋aked Objects允許團(tuán)隊(duì)非常自然地構(gòu)建一個(gè)明確無(wú)處不在的語(yǔ)言。隨著域?qū)拥慕?,團(tuán)隊(duì)可以根據(jù)需要開發(fā)更加量身定制的表示層。
lace an order
“落盤”
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。