限界上下文封裝了應(yīng)用邊界
架構(gòu)師在劃分限界上下文時(shí)可柿,不能只滿(mǎn)足于業(yè)務(wù)邊界的確立丙者,還得從控制技術(shù)復(fù)雜度的角度來(lái)考慮技術(shù)實(shí)現(xiàn)械媒,從而做出對(duì)系統(tǒng)質(zhì)量屬性的響應(yīng)與承諾,這種技術(shù)因素影響限界上下文劃分的例子可謂是不勝枚舉痢虹。
高并發(fā)
一個(gè)外賣(mài)系統(tǒng)的訂單業(yè)務(wù)與門(mén)店奖唯、支付等領(lǐng)域存在業(yè)務(wù)相關(guān)性糜值,然而考慮外賣(mài)業(yè)務(wù)的特殊性,它往往會(huì)在某個(gè)特定的時(shí)間段比如中午 11 點(diǎn)到 13 點(diǎn)會(huì)達(dá)到訂單量的高峰值瓢阴。系統(tǒng)面臨高并發(fā)壓力荣恐,同時(shí)還需要快速地處理每一筆外賣(mài)訂單累贤,與電商系統(tǒng)的訂單業(yè)務(wù)不同臼膏,外賣(mài)訂單具有周期短的時(shí)效性,必須在規(guī)定較短的時(shí)間內(nèi)走完從下訂單嚷硫、支付、門(mén)店接單到配送等整個(gè)流程脆贵。如果我們將訂單業(yè)務(wù)從整個(gè)系統(tǒng)中剝離出來(lái)卖氨,作為一個(gè)單獨(dú)的限界上下文對(duì)其進(jìn)行設(shè)計(jì)负懦,就可以從物理架構(gòu)上保證它的獨(dú)立性纸厉,在資源分配上做到高優(yōu)先級(jí)地?cái)U(kuò)展,在針對(duì)領(lǐng)域進(jìn)行設(shè)計(jì)時(shí)村斟,盡可能地引入異步化與并行化,來(lái)提高服務(wù)的響應(yīng)能力孩灯。
功能重用
對(duì)于一個(gè)面向企業(yè)雇員的國(guó)際報(bào)稅系統(tǒng)峰档,報(bào)稅業(yè)務(wù)、旅游業(yè)務(wù)與 Visa 業(yè)務(wù)都需要賬戶(hù)功能的支撐掀亩。系統(tǒng)對(duì)用戶(hù)的注冊(cè)與登錄有較為復(fù)雜的業(yè)務(wù)處理流程槽棍。對(duì)于一個(gè)新用戶(hù)而言抬驴,系統(tǒng)會(huì)向客戶(hù)企業(yè)的雇員發(fā)送邀請(qǐng)信布持,收到邀請(qǐng)信的用戶(hù)只有通過(guò)了問(wèn)題驗(yàn)證才能成為合法的注冊(cè)用戶(hù),否則該用戶(hù)的賬戶(hù)就會(huì)被鎖定按傅,稱(chēng)之為 Registration Locked唯绍。在用戶(hù)使用期間,若違背了系統(tǒng)要求的驗(yàn)證條件裂问,也可能會(huì)根據(jù)不同的條件鎖定賬戶(hù)堪簿,分別稱(chēng)之為 Soft Locked 和 Hard Locked皮壁。只有用戶(hù)提供了可以證明其合法身份的材料,其賬戶(hù)才能被解鎖虑瀑。
賬戶(hù)管理并非系統(tǒng)的核心領(lǐng)域舌狗,但與賬戶(hù)相關(guān)的業(yè)務(wù)邏輯卻相對(duì)復(fù)雜痛侍。從功能重用的角度考慮魔市,我們應(yīng)該將賬戶(hù)管理作為一個(gè)單獨(dú)的限界上下文待德,以滿(mǎn)足不同核心領(lǐng)域?qū)@一功能的重用,避免了重復(fù)開(kāi)發(fā)和重復(fù)代碼绘闷。
實(shí)時(shí)性
在電商系統(tǒng)中簸喂,商品自然是核心燎潮,而價(jià)格(Price)則是商品概念的一個(gè)重要屬性确封。倘若僅僅從業(yè)務(wù)的角度考慮再菊,在進(jìn)行領(lǐng)域建模時(shí)纠拔,價(jià)格僅僅是一個(gè)普通的領(lǐng)域值對(duì)象泛豪,可倘若該電商系統(tǒng)的商品數(shù)量達(dá)到數(shù)十億種诡曙,每天獲取商品信息的調(diào)用量在峰值達(dá)到數(shù)億乃至數(shù)百億次時(shí)价卤,價(jià)格就不再是業(yè)務(wù)問(wèn)題,而變成了技術(shù)問(wèn)題床嫌。對(duì)價(jià)格的每一次變更都需要及時(shí)同步厌处,真實(shí)地反饋給電商客戶(hù)盖文。
為了保證這種在高并發(fā)情況下的實(shí)時(shí)性五续,我們就需要專(zhuān)門(mén)針對(duì)價(jià)格領(lǐng)域提供特定的技術(shù)方案龄恋,例如郭毕,通過(guò)讀寫(xiě)分離、引入 Redis 緩存扳肛、異步數(shù)據(jù)同步等設(shè)計(jì)方法挖息。此時(shí)兽肤,價(jià)格領(lǐng)域?qū)⒆鳛橐粋€(gè)獨(dú)立的限界上下文,形成自己與眾不同的架構(gòu)方案幢码,同時(shí)症副,為價(jià)格限界上下文提供專(zhuān)門(mén)的資源政基,并在服務(wù)設(shè)計(jì)上保證無(wú)狀態(tài),從而滿(mǎn)足快速擴(kuò)容的架構(gòu)約束腋么。
第三方服務(wù)集成
一個(gè)電商系統(tǒng)需要支持多種常見(jiàn)的支付渠道咕娄,如微信支付、支付寶珊擂、中國(guó)銀聯(lián)以及各大主要銀行的支付圣勒。買(mǎi)家在購(gòu)買(mǎi)商品以及進(jìn)行退貨業(yè)務(wù)時(shí),可以選擇適合自己的支付渠道完成支付摧扇。電商系統(tǒng)需要與這些第三方支付系統(tǒng)進(jìn)行集成圣贸。不同的支付系統(tǒng)公開(kāi)的 API 并不相同,安全扛稽、加密以及支付流程對(duì)支付的要求也不相同吁峻。
在技術(shù)實(shí)現(xiàn)上在张,一方面我們希望為支付服務(wù)的客戶(hù)端提供完全統(tǒng)一的支付接口用含,以保證調(diào)用上的便利性與一致性,另一方面我們希望能解除第三方支付服務(wù)與電商系統(tǒng)內(nèi)部模塊之間的耦合帮匾,避免引起“供應(yīng)商鎖定(Vender Lock)”啄骇,也能更好地應(yīng)對(duì)第三方支付服務(wù)的變化。因此瘟斜,我們需要將這種集成劃分為一個(gè)單獨(dú)的限界上下文缸夹。
遺留系統(tǒng)
當(dāng)我們?cè)谶\(yùn)用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)對(duì)北美醫(yī)療內(nèi)容管理系統(tǒng)提出的新需求進(jìn)行設(shè)計(jì)與開(kāi)發(fā)時(shí),這個(gè)系統(tǒng)的已有功能已經(jīng)運(yùn)行了數(shù)年時(shí)間螺句。我們的任務(wù)是在現(xiàn)有系統(tǒng)中增加一個(gè)全新的 Find & Replace 模塊虽惭,其目的是為系統(tǒng)中的醫(yī)療內(nèi)容提供針對(duì)醫(yī)療術(shù)語(yǔ)、藥品以及藥品成分的查詢(xún)與替換蛇尚。這個(gè)系統(tǒng)已經(jīng)定義了自己的領(lǐng)域模型芽唇。這些領(lǐng)域模型與新增模塊的領(lǐng)域有相似之處。但是佣蓉,為了避免已有模型對(duì)新開(kāi)發(fā)模塊的影響披摄,我們應(yīng)該將這些已有功能視為具有技術(shù)債的遺留系統(tǒng)亲雪,并將該遺留系統(tǒng)整體視為一個(gè)限界上下文。
通過(guò)這個(gè)遺留系統(tǒng)限界上下文的邊界保護(hù)疚膊,就可以避免我們?cè)陂_(kāi)發(fā)過(guò)程中陷入遺留系統(tǒng)龐大代碼庫(kù)的泥沼义辕。由于新增需求與原有系統(tǒng)在業(yè)務(wù)上存在交叉功能,因而可能失去了部分代碼的重用機(jī)會(huì)寓盗,卻能讓我們甩開(kāi)遺留系統(tǒng)的束縛灌砖,放開(kāi)雙手運(yùn)用領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)的思想建立自己的領(lǐng)域模型與架構(gòu)。只有在需要調(diào)用遺留系統(tǒng)的時(shí)候傀蚌,作為調(diào)用者站在遺留系統(tǒng)限界上下文之外基显,去思考我們需要的服務(wù),然后酌情地考慮模型對(duì)象之間的轉(zhuǎn)換以及服務(wù)接口的提取善炫。
如上的諸多案例都是從技術(shù)層面而非業(yè)務(wù)層面為系統(tǒng)劃分了應(yīng)用邊界撩幽,這種邊界是由限界上下文完成的,通過(guò)它形成了對(duì)技術(shù)實(shí)現(xiàn)的隔離箩艺,避免不同的技術(shù)方案選擇互相干擾導(dǎo)致架構(gòu)的混亂窜醉。
案例:生成稅務(wù)報(bào)告的技術(shù)風(fēng)險(xiǎn)
國(guó)際稅務(wù)系統(tǒng)需要在政府指定的周期提交稅務(wù)報(bào)告,凡是滿(mǎn)足條件的 Assignee 都需要在規(guī)定時(shí)間內(nèi)生成稅務(wù)報(bào)告艺谆。在生成稅務(wù)報(bào)告時(shí)榨惰,需要對(duì) Assignee 提交的 Questionnaire 數(shù)據(jù)進(jìn)行合并,并基于稅收策略與 Assignee 個(gè)人情況執(zhí)行計(jì)算静汤。生成稅務(wù)報(bào)告的時(shí)序圖如下所示:
代碼如下所示:
public class TaxReportGenerator {? ? @Service
? ? private HtmlReportProvider provider;? ? @Service
? ? private PdfConverter converter;? ? @Repository
? ? private ReportRepository repository;? ? public void generateReports(String calendarReportName) {
? ? ? ? Byte[]? bytes = provider.getHtmlBytes(calendarReportName);
? ? ? ? Byte[] pdfBytes = converter.getPdfBytes(bytes, provider.getTitle());
? ? ? ? repository.save(new TaxReport(pdfBytes));
? ? }
}
由于每個(gè) Assignee 的報(bào)告內(nèi)容多琅催,生成的 PDF 文件較大,使得生成稅務(wù)報(bào)告的單位時(shí)間也較長(zhǎng)虫给。在最初用戶(hù)量較少的情況下藤抡,所有稅務(wù)報(bào)告的生成時(shí)間在客戶(hù)預(yù)期范圍內(nèi),因而并未針對(duì)報(bào)告生成功能做特別的架構(gòu)設(shè)計(jì)狰右。后來(lái)杰捂,隨著系統(tǒng)的 Assignee 用戶(hù)數(shù)增多,在提交稅務(wù)報(bào)告的高峰期時(shí)棋蚌,報(bào)告生成的時(shí)間越來(lái)越長(zhǎng)。以高峰期需要提交 2000 個(gè)稅務(wù)報(bào)告為例挨队,如果每個(gè)稅務(wù)報(bào)告的提交時(shí)間為 1 分鐘谷暮,在只有一個(gè) worker 的情況下,我們需要2000*1/60=33小時(shí)盛垦。
由于單個(gè)稅務(wù)報(bào)告的生成性能已經(jīng)達(dá)到瓶頸湿弦,沒(méi)有優(yōu)化的空間,因而需要在架構(gòu)層面對(duì)方案進(jìn)行優(yōu)化腾夯,包括如下兩方面:
引入消息隊(duì)列颊埃,將整個(gè)稅務(wù)報(bào)告生成過(guò)程拆分為消息隊(duì)列的生產(chǎn)者和消費(fèi)者蔬充。處于應(yīng)用服務(wù)器一端的生產(chǎn)者僅負(fù)責(zé)收集稅務(wù)報(bào)告需要的數(shù)據(jù),而將生成報(bào)告的職責(zé)交給消息隊(duì)列的消費(fèi)者班利,從而減輕應(yīng)用服務(wù)器的壓力饥漫。
將報(bào)告生成識(shí)別為限界上下文,定義為可以單獨(dú)部署的微服務(wù)罗标,以便于靈活地實(shí)現(xiàn)水平擴(kuò)展庸队。
如下圖是我們基于技術(shù)實(shí)現(xiàn)識(shí)別出來(lái)的 report 限界上下文。在上下文邊界內(nèi)闯割,引入了消息隊(duì)列彻消。server 作為生成者,在收集了稅務(wù)數(shù)據(jù)后組裝消息宙拉,然后將消息入隊(duì)宾尚;作為消費(fèi)者的 worker 訂閱該消息,一旦消息傳遞到達(dá)谢澈,則負(fù)責(zé)生成報(bào)告:
無(wú)論是 server 還是 worker央勒,皆為并行執(zhí)行,且在理論上可以無(wú)限制地水平擴(kuò)展澳化。倘若在性能上無(wú)法滿(mǎn)足要求崔步,我們可以增加 server 或 worker 節(jié)點(diǎn)。例如缎谷,我們希望所有稅務(wù)報(bào)告能夠在 4 小時(shí)內(nèi)處理完畢井濒,通過(guò)公式2000*1/60/4計(jì)算,預(yù)估需要 7 個(gè) worker 并行執(zhí)行即可滿(mǎn)足目標(biāo)列林。
分享交流:
我們?yōu)榉窒斫涣鲃?chuàng)建了微信交流群瑞你,以方便更有針對(duì)性地討論課程相關(guān)問(wèn)題。入群方式請(qǐng)?zhí)砑幼髡呶⑿盘?hào):「Robynn-D」希痴,并注明「DDD」者甲,謝謝~
本文首發(fā):