偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

萬億級(jí)流量下怎樣保證系統(tǒng)不宕機(jī)?

網(wǎng)絡(luò) 網(wǎng)絡(luò)管理 架構(gòu)
本文在對(duì)歷史遺留系統(tǒng)問題深入分析后,找到問題的本質(zhì)解,制定切實(shí)有效的解決方案。通過運(yùn)用整潔架構(gòu)設(shè)計(jì)理念,對(duì)歷史遺留系統(tǒng)進(jìn)行全面的模型分析和架構(gòu)重新設(shè)計(jì),并借助DDD戰(zhàn)術(shù)工具實(shí)現(xiàn)代碼開發(fā)。
最近,身邊好幾位在創(chuàng)業(yè)公司的技術(shù)朋友都在抱怨,說他們公司的系統(tǒng)不穩(wěn)定,代碼年代久遠(yuǎn),還找不到完整的設(shè)計(jì)文檔,現(xiàn)在接手后連碰都不敢碰那些陳舊的代碼。面對(duì)這些歷史遺留問題,我給他們講了一個(gè)身邊真實(shí)的故事。

以前有家公司,業(yè)務(wù)發(fā)展特別快,現(xiàn)有的系統(tǒng)功能跟不上市場(chǎng)需求了。于是,他們收購(gòu)了一家小公司,并讓自己公司的技術(shù)團(tuán)隊(duì)去接手被收購(gòu)公司的系統(tǒng)。結(jié)果呢,技術(shù)團(tuán)隊(duì)花了好大一番功夫梳理代碼,發(fā)現(xiàn)那代碼亂得不行,簡(jiǎn)直就是 “意大利面條” 式的代碼,根本沒法維護(hù)。沒辦法,他們只能下定決心重構(gòu)這個(gè)系統(tǒng)。

半年后,業(yè)務(wù)又變了,這個(gè)系統(tǒng)要交給公司另一個(gè)部門的技術(shù)團(tuán)隊(duì)來維護(hù)。新接手的團(tuán)隊(duì)同樣先梳理代碼,梳理完后得出的結(jié)論是:這代碼可讀性極差,層級(jí)關(guān)系混亂,完全沒辦法繼續(xù)開發(fā)新功能了,所以希望再對(duì)這個(gè)系統(tǒng)進(jìn)行重構(gòu)。這時(shí)候,公司老板找來了兩個(gè)技術(shù)團(tuán)隊(duì)的負(fù)責(zé)人,問他們:“這個(gè)系統(tǒng)不是半年前剛重構(gòu)過的嗎?怎么換了個(gè)團(tuán)隊(duì)維護(hù)就要重構(gòu)呢?你們這次重構(gòu)了,就能保證下次別人接手不再吐槽、不再重構(gòu)嗎?”

聽完這個(gè)故事后,朋友們沉默了一會(huì)兒,然后問我:“那我們?cè)撛趺崔k呢?現(xiàn)在每周因?yàn)檫@些遺留系統(tǒng)導(dǎo)致的問題,搞得我焦頭爛額的?!?/p>

圖1 混亂不堪的系統(tǒng)現(xiàn)狀圖1 混亂不堪的系統(tǒng)現(xiàn)狀

不只是創(chuàng)業(yè)公司,這種情況在大公司也很常見,甚至可以說更加普遍。由于大公司的業(yè)務(wù)發(fā)展時(shí)間長(zhǎng)、系統(tǒng)復(fù)雜度高,遺留下來的老舊系統(tǒng)數(shù)量更多,且這些系統(tǒng)的歷史往往更為久遠(yuǎn)。

下圖是某知名互聯(lián)網(wǎng)公司對(duì)其線上事故原因進(jìn)行統(tǒng)計(jì)分析后得出的結(jié)果。從圖中可以看出,接近一半的線上事故問題,都可以追溯到業(yè)務(wù)代碼邏輯的缺陷或不合理之處。這進(jìn)一步凸顯了代碼質(zhì)量和架構(gòu)合理性在軟件系統(tǒng)穩(wěn)定運(yùn)行中的重要性。

圖2 某互聯(lián)網(wǎng)公司線上事故分析圖圖2 某互聯(lián)網(wǎng)公司線上事故分析圖

造成系統(tǒng)問題的原因和解決方案

運(yùn)維抱怨道:“這些歷史遺留系統(tǒng)根本沒法維護(hù),動(dòng)不動(dòng)就宕機(jī),三天兩頭出問題?!?/p>

開發(fā)人員也無奈地說:“老功能動(dòng)都不敢動(dòng),怕出問題,新功能更是沒法添加,每天都在忙著救火,搞穩(wěn)定性治理,忙得焦頭爛額?!?/p>

測(cè)試人員則表示:“一個(gè)接口或者消息,根本不知道有多少地方在使用,完全不清楚哪些部分會(huì)受影響,更別提會(huì)不會(huì)有漏測(cè)的地方,心里一點(diǎn)底都沒有?!?/p>

我相信,大家在面對(duì)各種歷史遺留問題時(shí),如果詳細(xì)羅列出來,恐怕都能寫好幾篇論文了。不過,這些所謂的“歷史遺留問題”,從一開始就是問題嗎?或者說,如果原開發(fā)團(tuán)隊(duì)還在維護(hù)這些系統(tǒng),對(duì)他們來說這些問題還會(huì)存在嗎?我覺得未必。那究竟是什么原因?qū)е逻@些問題一步步演變成現(xiàn)在這種難以維護(hù)的局面呢?顯然,壓死駱駝的絕不是最后一根稻草。

圖3 壓死駱駝的稻草圖3 壓死駱駝的稻草

因此,面對(duì)形形色色的歷史遺留問題,我們首先要做的是透過現(xiàn)象看清本質(zhì)。只有深入挖掘問題的根本原因,才能找到真正有效的解決方案,從而給出切實(shí)可行的具體措施來解決這些問題。

圖4 U型思考模型圖4 U型思考模型

對(duì)于“歷史遺留問題”,我們計(jì)劃運(yùn)用 U 型思考模型的四步法展開分析。我們期望從問題的根本本質(zhì)出發(fā),盡力擺脫僅針對(duì)表面現(xiàn)象進(jìn)行應(yīng)急處理的不良循環(huán)。

1.定義核心問題

無論是歷史遺留系統(tǒng)引發(fā)的訂單丟失、賬務(wù)對(duì)賬錯(cuò)誤等資損問題,還是內(nèi)存泄漏、線程死鎖等系統(tǒng)性能問題,以及老功能修改困難、新需求開發(fā)受阻等效率問題,這些可能都只是表面現(xiàn)象。為什么這么說呢?因?yàn)檫@些問題都是當(dāng)前系統(tǒng)在特定時(shí)間、地點(diǎn)和條件下出現(xiàn)的具體表現(xiàn),而非問題的根源所在。

舉個(gè)例子,假設(shè)今天出現(xiàn)了一筆訂單,由于支付超時(shí)失敗,系統(tǒng)沒能及時(shí)提醒用戶重新支付,導(dǎo)致用戶端一直顯示“支付中”。如果我們僅把這些表面現(xiàn)象當(dāng)作核心問題,可能會(huì)通過延長(zhǎng)支付超時(shí)時(shí)間或增加失敗重試機(jī)制來修改代碼。然而,明天可能又會(huì)出現(xiàn)因支付失敗重試機(jī)制導(dǎo)致的重復(fù)扣款問題。如此一來,我們就陷入了每天不斷應(yīng)對(duì)各種歷史遺留問題的循環(huán),疲于奔命。

或許有人會(huì)說,這都是當(dāng)初架構(gòu)設(shè)計(jì)不合理造成的,現(xiàn)在出現(xiàn)的各種問題都是這個(gè)原因。這種說法有其合理性,因?yàn)楫?dāng)下的許多問題確實(shí)可以追溯到架構(gòu)設(shè)計(jì)的缺陷。但同時(shí),這種說法也有局限性。我們不能簡(jiǎn)單地將所有問題都?xì)w咎于一個(gè)模糊的概念,因?yàn)檫@樣做不利于我們深入挖掘問題的本質(zhì)。這就像是小區(qū)里發(fā)生盜竊事件,我們不能僅僅歸因于“人性本惡”,而忽視了小區(qū)安保松懈、外來人員隨意進(jìn)出等更直接的原因。

那么,面對(duì)歷史遺留系統(tǒng),我們真正需要解決的核心問題是什么呢?我認(rèn)為,關(guān)鍵在于如何避免我們的系統(tǒng)在未來成為別人眼中的歷史遺留問題。這個(gè)觀點(diǎn)值得我們深入思考。

2.發(fā)現(xiàn)問題本質(zhì)

當(dāng)我們確定要解決的核心問題后,接下來要深入洞察問題的本質(zhì)原因。就像我在文章開頭提到的例子,為什么我們接手別人開發(fā)的系統(tǒng)時(shí),常常覺得代碼混亂而不敢輕易修改?而當(dāng)我們重構(gòu)后的系統(tǒng)交給其他團(tuán)隊(duì)維護(hù)時(shí),他們?yōu)楹我矔?huì)覺得代碼難以理解?難道是我們比之前的人更厲害,而后面的人又比我們厲害?顯然不是。這主要是因?yàn)槊總€(gè)人的編碼習(xí)慣和設(shè)計(jì)風(fēng)格不同。就像中國(guó)小學(xué)生和印度小學(xué)生做乘法的方式不同,我們可能完全看不懂印度小學(xué)生的計(jì)算過程,自然也不敢輕易修改。

圖5 傳統(tǒng)乘法 VS 印度乘法圖5 傳統(tǒng)乘法 VS 印度乘法

在軟件的開發(fā)和維護(hù)過程中,其生命周期往往從最初的理想狀態(tài)逐漸向復(fù)雜、混亂和無序狀態(tài)演變,最終因不可維護(hù)而被迫下線或需要重構(gòu)。這種導(dǎo)致軟件質(zhì)量逐步惡化的因素,被稱為軟件的熵增現(xiàn)象。隨著歷史遺留系統(tǒng)的不斷運(yùn)行,代碼功能被頻繁修改和補(bǔ)充,時(shí)間一長(zhǎng),后續(xù)的開發(fā)者就越難理解最初的代碼邏輯。

圖6 生活中的熵增圖6 生活中的熵增

熵的概念最早起源于物理學(xué),用于度量一個(gè)熱力學(xué)系統(tǒng)的無序程度。熱力學(xué)第二定律,又稱“熵增定律”,表明了在自然過程中,一個(gè)孤立的系統(tǒng)總是從最初的集中、有序的排列狀態(tài),趨向于分散、混亂和無序;當(dāng)熵達(dá)到最大時(shí),系統(tǒng)就會(huì)處于一種靜寂狀態(tài)。

因此,想讓歷史遺留系統(tǒng)保持穩(wěn)定,既能順暢維護(hù),又能順利支持新老功能的開發(fā),關(guān)鍵在于以團(tuán)隊(duì)熟悉的風(fēng)格來設(shè)計(jì)和開發(fā)系統(tǒng),使代碼與結(jié)構(gòu)清晰直觀,確保代碼能如實(shí)體現(xiàn)架構(gòu)設(shè)計(jì),力求達(dá)到“代碼即設(shè)計(jì)”的境界??偟膩碚f,就是要保持架構(gòu)模式的統(tǒng)一,確保代碼清晰且如實(shí)映射架構(gòu)設(shè)計(jì)。

3.找到問題的本質(zhì)解

在探索如何找到歷史遺留系統(tǒng)問題的根本解決方案之前,我們不妨借助時(shí)光機(jī),回到上世紀(jì) 60 - 70 年代,一同梳理計(jì)算機(jī)軟件工程的發(fā)展脈絡(luò),答案其實(shí)就深藏于軟件工程的演變歷程中。當(dāng)提及軟件工程時(shí),就不得不回顧軟件歷史上兩次極具影響力的 “軟件危機(jī)”。

軟件危機(jī)是指在軟件開發(fā)及維護(hù)過程中遭遇的一系列棘手問題,這些問題可能大幅縮短軟件產(chǎn)品的使用壽命,甚至使其徹底報(bào)廢。

  • 第一次軟件危機(jī)(20 世紀(jì) 60 - 70 年代) :當(dāng)時(shí)軟件開發(fā)主要依賴機(jī)器語(yǔ)言或匯編語(yǔ)言,針對(duì)特定機(jī)器進(jìn)行設(shè)計(jì)與編寫。軟件規(guī)模較小,無需系統(tǒng)化開發(fā)方法,多為個(gè)人設(shè)計(jì)、編碼與使用。程序依賴特定機(jī)器硬件特性,代碼復(fù)雜難懂且不可移植,難以開發(fā)復(fù)雜功能軟件。1968 年,北大西洋公約組織的計(jì)算機(jī)科學(xué)家在聯(lián)邦德國(guó)召開國(guó)際會(huì)議,首度提出“軟件工程”一詞,標(biāo)志著這門新興工程學(xué)科的誕生,旨在研究和攻克軟件危機(jī)。在此階段,更高級(jí)的結(jié)構(gòu)化編程語(yǔ)言如 C 語(yǔ)言(1972 年誕生)相繼出現(xiàn),結(jié)構(gòu)化編程思想占據(jù)主導(dǎo),提升了代碼的可讀性和可維護(hù)性。
  • 第二次軟件危機(jī)(20 世紀(jì) 80 - 90 年代) :此次危機(jī)源于軟件復(fù)雜性的進(jìn)一步提升。大規(guī)模軟件動(dòng)輒數(shù)百萬行代碼,涉及上百名程序員。如何高效、可靠地構(gòu)建和維護(hù)此類規(guī)模軟件成為新難題。例如,《人月神話》中提到的IBM 公司開發(fā)的OS/360 系統(tǒng),雖投入巨大資源,仍延期交付且存在大量錯(cuò)誤。當(dāng)時(shí)人們期望軟件代碼具備可組合性、可擴(kuò)展性和可維護(hù)性。盡管結(jié)構(gòu)化編程改善了代碼可讀性,但從開發(fā)者視角出發(fā),主要運(yùn)用數(shù)據(jù)流圖(DFD)進(jìn)行系統(tǒng)分析,難以直觀反映現(xiàn)實(shí)場(chǎng)景,導(dǎo)致開發(fā)人員與用戶溝通困難,需求分析師應(yīng)運(yùn)而生。為應(yīng)對(duì)此次危機(jī),面向?qū)ο缶幊陶Z(yǔ)言(如 C++、C#、Java 等)誕生,同時(shí)催生了設(shè)計(jì)模式、重構(gòu)、測(cè)試、需求分析等更優(yōu)的軟件工程方法。

鑒于如今系統(tǒng)愈發(fā)復(fù)雜,需求不確定性增加,在當(dāng)今 VUCA 時(shí)代(易變性、不確定性、復(fù)雜性、模糊性),回顧軟件危機(jī)及軟件工程發(fā)展歷程顯得尤為重要。面對(duì)復(fù)雜混亂的歷史遺留系統(tǒng)問題,軟件工程宛如一劑良方。維基百科定義軟件工程涵蓋 “軟件開發(fā)技術(shù)” 和 “軟件項(xiàng)目管理” 兩方面。其中,“軟件項(xiàng)目管理” 主要涉及項(xiàng)目管理知識(shí)體系,如 RUP 和敏捷 Scrum 等,暫非本次重點(diǎn)?!败浖_發(fā)技術(shù)” 才是我們關(guān)注的核心,包括結(jié)構(gòu)化編程、面向?qū)ο箝_發(fā)、MVC、領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)、整潔架構(gòu)等開發(fā)方法。以下是按時(shí)間維度梳理的部分常見軟件開發(fā)方法:

圖7 常見軟件開發(fā)方法圖7 常見軟件開發(fā)方法

在軟件開發(fā)中,有多種方法可供選擇,團(tuán)隊(duì)可以根據(jù)項(xiàng)目和自身情況靈活運(yùn)用。重要的是在團(tuán)隊(duì)內(nèi)保持統(tǒng)一的架構(gòu)模式,確保代碼風(fēng)格一致,避免因風(fēng)格差異導(dǎo)致成員間難以理解彼此代碼。

有些工程師只關(guān)注完成功能需求并交付,忽視軟件的維護(hù)和擴(kuò)展性,這是系統(tǒng)一兩年后難以維護(hù)的常見原因。其實(shí),軟件開發(fā)不僅要寫代碼,更要重視需求分析,理解需求本質(zhì),與團(tuán)隊(duì)統(tǒng)一概念和語(yǔ)言,實(shí)現(xiàn)有效溝通。

  • 需求分析是軟件開發(fā)的關(guān)鍵階段。開發(fā)人員應(yīng)積極參與需求討論,運(yùn)用第一性原理思考,洞察需求本質(zhì)。與團(tuán)隊(duì)對(duì)齊專有名詞和關(guān)鍵問題,達(dá)成統(tǒng)一認(rèn)知。這一階段產(chǎn)出需求規(guī)約文檔,對(duì)應(yīng)面向?qū)ο蠓治觯∣OA)階段,為后續(xù)設(shè)計(jì)和編碼提供依據(jù)。
  • 接下來是面向?qū)ο笤O(shè)計(jì)(OOD)階段。根據(jù)需求規(guī)約文檔,設(shè)計(jì)清晰合理的技術(shù)方案,確定系統(tǒng)的類結(jié)構(gòu)、對(duì)象交互和模塊劃分,確保設(shè)計(jì)邏輯清晰,為編碼提供明確指導(dǎo)。
  • 最后是面向?qū)ο缶幋a(OOP)階段。需按照OOD階段的設(shè)計(jì)方案編寫代碼,確保代碼完整反映設(shè)計(jì)邏輯。注重代碼的可讀性和可維護(hù)性,遵循團(tuán)隊(duì)統(tǒng)一的編碼規(guī)范。

通過遵循OOA、OOD和OOP的流程,團(tuán)隊(duì)可以更好地管理軟件開發(fā)項(xiàng)目,提高代碼質(zhì)量,增強(qiáng)系統(tǒng)的可維護(hù)性和擴(kuò)展性。

4.解決問題的有效方案

軟件開發(fā)技術(shù)飛速進(jìn)步,開發(fā)方法學(xué)也持續(xù)更新,比如水平分層從 MVC 三層架構(gòu)到 DDD 四層架構(gòu)、CQRS架構(gòu),垂直拆分從單體架構(gòu)到 SOA、微服務(wù)架構(gòu)(MSA)、服務(wù)網(wǎng)格(Service Mesh)等。萬變不離其宗,這些方法學(xué)旨在應(yīng)對(duì)系統(tǒng)復(fù)雜性,確保代碼可開發(fā)、易維護(hù),防止軟件系統(tǒng)陷入 “危機(jī)”。

以下是某真實(shí)項(xiàng)目工程代碼結(jié)構(gòu)(公司類似工程眾多)。正如那句調(diào)侃:“三個(gè)月前寫這段代碼時(shí),只有上帝和我懂;如今,恐怕只有上帝還看得懂了?!?/p>

圖8 某真實(shí)工程代碼現(xiàn)狀圖8 某真實(shí)工程代碼現(xiàn)狀

軟件開發(fā)大師 Bob 大叔(Robert C. Martin)在其著作《整潔架構(gòu)之道》中,提出了一個(gè)衡量軟件架構(gòu)設(shè)計(jì)質(zhì)量的標(biāo)準(zhǔn):

一個(gè)系統(tǒng)架構(gòu)的優(yōu)劣可以通過滿足用戶需求的成本來判斷。若在系統(tǒng)整個(gè)生命周期內(nèi),需求變更成本始終很低,那么這個(gè)設(shè)計(jì)就是好的。反之,如果每次發(fā)布都會(huì)增加后續(xù)變更的成本,那么這個(gè)設(shè)計(jì)就有問題。

關(guān)于整潔架構(gòu)圖的介紹如下:

圖9 整潔架構(gòu)圖圖9 整潔架構(gòu)圖

1.Entities(實(shí)體) :包含核心的業(yè)務(wù)規(guī)則,擁有狀態(tài)屬性以及業(yè)務(wù)邏輯操作。

2.Application(應(yīng)用層) :僅包含業(yè)務(wù)流程,代表各種用例場(chǎng)景,主要負(fù)責(zé)編排和調(diào)度 Entities 中的業(yè)務(wù)邏輯操作,具體的實(shí)現(xiàn)會(huì)在后續(xù)案例代碼中展示。

3.接口依賴方向 :所有接口(Controllers/Gateways/Presenters)只能向圓圈內(nèi)部依賴,不能反向依賴。例如,Controller 可以調(diào)用 Application,但 Application 不能調(diào)用 Controller 接口。對(duì)于數(shù)據(jù)庫(kù)操作,以往常常直接將 Entity 領(lǐng)域?qū)ο髠鬟f到 Mapper 對(duì)象中,導(dǎo)致領(lǐng)域?qū)ο髧?yán)重依賴數(shù)據(jù)庫(kù)操作對(duì)象。在 DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))中,應(yīng)通過依賴反轉(zhuǎn)來改變這種現(xiàn)象(整潔架構(gòu)借鑒了 DDD 的很多概念,比如 Repository 等)。

4.Use Cases(用例) :是梳理和理解需求的重要工具,它代表業(yè)務(wù)場(chǎng)景,是架構(gòu)設(shè)計(jì)中非常關(guān)鍵的概念,不懂得用例的架構(gòu)師難以進(jìn)行有效的架構(gòu)設(shè)計(jì)。

現(xiàn)在,讓我們重新聚焦于核心問題。我認(rèn)為,大家都能完成功能需求開發(fā),也都會(huì)使用各種開發(fā)工具,遵守編碼規(guī)范。然而,為何仍有眾多遺留系統(tǒng)問題困擾著我們?這些遺留系統(tǒng),有時(shí)并非我們從其他團(tuán)隊(duì)接手的復(fù)雜代碼,而是我們自己團(tuán)隊(duì)在半年或一年前編寫的代碼,如今卻難以理解和維護(hù)。

因此,我希望每位程序員不僅要掌握“軟件開發(fā)的工具和語(yǔ)言環(huán)境”,更要靈活運(yùn)用“軟件開發(fā)的方法”,實(shí)現(xiàn)知行合一。在此案例中,我們將積極踐行《整潔代碼》和《整潔架構(gòu)設(shè)計(jì)之道》,致力于提升代碼的可讀性,降低服務(wù)組件的依賴耦合性,使變更的影響局限在最小的單元范圍內(nèi)。最后,通過一張圖來簡(jiǎn)潔地總結(jié)我們解決歷史遺留系統(tǒng)問題的思考路徑和方法:

圖10 系統(tǒng)化解決問題思考方法圖10 系統(tǒng)化解決問題思考方法

在系統(tǒng)開發(fā)中踐行整潔架構(gòu)之道

在項(xiàng)目開發(fā)中,我們也希望工程代碼結(jié)構(gòu)清晰、整潔舒適。接下來,我將介紹整潔架構(gòu)在會(huì)員系統(tǒng)中的實(shí)踐經(jīng)驗(yàn),包括需求分析的用例梳理(OOA)、領(lǐng)域模型設(shè)計(jì)和程序設(shè)計(jì)(OOD),以及代碼編寫(OOP)等內(nèi)容。

1.項(xiàng)目背景簡(jiǎn)單介紹

會(huì)員系統(tǒng)是企業(yè)或商家提升用戶忠誠(chéng)度和銷售額的有效手段,其主要特點(diǎn)如下。

  • 會(huì)員注冊(cè):用戶可以通過會(huì)員系統(tǒng)注冊(cè)為會(huì)員,會(huì)員系統(tǒng)將收集用戶的個(gè)人信息,并為其生成唯一的會(huì)員賬號(hào)(通常為手機(jī)號(hào)碼)。
  • 會(huì)員信息管理:對(duì)會(huì)員的詳細(xì)信息進(jìn)行管理,包括個(gè)人資料、會(huì)員狀態(tài)和消費(fèi)歷史等。
  • 獎(jiǎng)勵(lì)和折扣管理:基于會(huì)員的忠誠(chéng)度、購(gòu)買歷史或其他標(biāo)準(zhǔn)為會(huì)員設(shè)計(jì)獎(jiǎng)勵(lì)和折扣。
  • 積分和獎(jiǎng)勵(lì):會(huì)員在滿足一定條件的消費(fèi)條件后可以獲得積分或獎(jiǎng)勵(lì),并可在未來的消費(fèi)中使用這些積分或獎(jiǎng)勵(lì)。會(huì)員系統(tǒng)應(yīng)在結(jié)賬時(shí)顯示可用的積分,并方便用戶使用。
  • 會(huì)員觸達(dá):通過電子郵件或短信等方式向會(huì)員發(fā)送促銷活動(dòng)通知。

2.通過用例梳理分析需求

了解項(xiàng)目背景后,通常下一步是編寫需求文檔,也就是PRD文檔。但許多PRD文檔是用自然語(yǔ)言編寫的,這使得閱讀和理解變得較為困難,還容易遺漏一些業(yè)務(wù)流程和場(chǎng)景。

那么,除了使用PRD這類自然語(yǔ)言編寫的需求文檔外,有沒有更直觀、易懂的方式來梳理業(yè)務(wù)需求呢?答案是肯定的,我們可以使用用例規(guī)約文檔來實(shí)現(xiàn)這個(gè)目的。

第一步:確定干系人

根據(jù)PRD文檔,以及和業(yè)務(wù)溝通交流,我們首先需要確定使用該系統(tǒng)的相關(guān)人員有哪些,以及他們對(duì)系統(tǒng)的目標(biāo)和訴求是什么?以下為簡(jiǎn)化的“干系人-目標(biāo)”圖表:

表 1

干系人

目    標(biāo)

描    述

企業(yè)經(jīng)營(yíng)者

(1)管理配置優(yōu)惠折扣活動(dòng)。

(2)查看促銷活動(dòng)的效果。

(3)制定會(huì)員等級(jí)規(guī)則。

(4)管理會(huì)員相關(guān)信息

企業(yè)經(jīng)營(yíng)者是會(huì)員系統(tǒng)項(xiàng)目的主要干系人,負(fù)責(zé)制定會(huì)員等級(jí)規(guī)則,設(shè)定獎(jiǎng)勵(lì)和折扣活動(dòng),管理會(huì)員信息和監(jiān)督項(xiàng)目的實(shí)施

收銀員

(1)查看會(huì)員信息和權(quán)益。

(2)幫助會(huì)員更好地注冊(cè)。

(3)幫助會(huì)員享受權(quán)益或兌換獎(jiǎng)勵(lì)

收銀員或服務(wù)員是企業(yè)與會(huì)員之間的橋梁。他們需要能夠訪問會(huì)員系統(tǒng),以便為會(huì)員解決問題和提供支持

用戶

(1)會(huì)員注冊(cè)。

(2)為會(huì)員卡儲(chǔ)值。

(3)享受會(huì)員權(quán)益,例如折扣等。

(4)參加促銷活動(dòng)

用戶是最終使用會(huì)員系統(tǒng)的干系人。他們需要通過會(huì)員系統(tǒng)注冊(cè)并領(lǐng)取獎(jiǎng)勵(lì)和折扣

技術(shù)開發(fā)者

(1)系統(tǒng)日志記錄,便于排查問題。

(2)用戶使用埋點(diǎn)分析

技術(shù)開發(fā)者可以提供對(duì)會(huì)員系統(tǒng)的技術(shù)支持。他們需要理解會(huì)員系統(tǒng)的工作原理,并能夠幫助企業(yè)解決出現(xiàn)的問題

……

……

……

在梳理項(xiàng)目干系人及其目標(biāo)時(shí),我們要全面覆蓋所有相關(guān)方,不能有所遺漏。例如,技術(shù)開發(fā)者的目標(biāo)和需求對(duì)系統(tǒng)開發(fā)也至關(guān)重要,不能忽視。在這一階段,項(xiàng)目干系人的目標(biāo)可能還比較籠統(tǒng),需要通過深入訪談來進(jìn)一步細(xì)化和完善。

第二步:設(shè)計(jì)概要用例圖

在完成“干系人 - 目標(biāo)”梳理后,接下來可以從這些目標(biāo)中提取用例名稱。因?yàn)槊總€(gè)用例都是為了實(shí)現(xiàn)某個(gè)干系人的一個(gè)具體目標(biāo)而存在的。同時(shí),一個(gè)用例往往包含多個(gè)場(chǎng)景,比如系統(tǒng)在成功或失敗等不同場(chǎng)景下的處理方式等。以下是一個(gè)簡(jiǎn)單的示例用例圖:

圖11 會(huì)員系統(tǒng)概要用例圖圖11 會(huì)員系統(tǒng)概要用例圖

有了這樣的概要用例,我們對(duì)系統(tǒng)需要具備什么樣的功能和能力就有了一個(gè)比較明確的方向了,當(dāng)然如果覺得這個(gè)用例太過于抽象了,那么我們可以再繼續(xù)梳理下一層稍微詳細(xì)點(diǎn)的用例,這里建議不要超過三層,因?yàn)槌^三層的會(huì)顯得過于細(xì)節(jié)化了,而我們?cè)谶@里主要是為了梳理清楚實(shí)現(xiàn)用戶目標(biāo)系統(tǒng)應(yīng)該具備的能力就足夠了。對(duì)于每個(gè)用例更詳細(xì)的將在接下來的“書寫核心用例”中介紹。

第三步:書寫核心用例

概要用例有助于我們從宏觀上把握系統(tǒng)的功能,但對(duì)研發(fā)團(tuán)隊(duì)來說,利用它不足以進(jìn)行研發(fā)工作。因此,在確定用例的優(yōu)先級(jí)之后,我們應(yīng)著手對(duì)核心且高價(jià)值的用例進(jìn)行詳細(xì)設(shè)計(jì),并編寫詳細(xì)的用例。一個(gè)詳細(xì)的用例通常包含以下幾部分。

◎用例名稱:簡(jiǎn)單明了地描述用例的主要功能。

◎參與者:與用例交互的用戶或其他系統(tǒng)。

◎前置條件:在執(zhí)行用例之前,系統(tǒng)必須處于什么狀態(tài)。

◎后置條件:在執(zhí)行用例之后,系統(tǒng)應(yīng)該處于什么狀態(tài)。

◎正常場(chǎng)景:描述用例在正常情況下的執(zhí)行流程。

◎異常場(chǎng)景:描述用例在異常情況下的執(zhí)行流程。

在編寫詳細(xì)用例時(shí),我們必須特別注意覆蓋所有正常場(chǎng)景和異常場(chǎng)景,以防遺漏場(chǎng)景。當(dāng)然,我們不必為每個(gè)用例都編寫詳細(xì)的文檔,只需對(duì)復(fù)雜的核心用例編寫詳細(xì)的文檔,這樣可以顯著提升效率。例如,注冊(cè)會(huì)員的詳細(xì)用例如表2所示。

表 2

名    稱

會(huì)員注冊(cè)

描述

作為用戶,我希望能夠輕松注冊(cè)為會(huì)員,以便享受積分獎(jiǎng)勵(lì)、折扣優(yōu)惠等會(huì)員權(quán)益

參與者

用戶:能夠享受會(huì)員權(quán)益、折扣及參與促銷活動(dòng),能夠購(gòu)買僅限會(huì)員購(gòu)買的商品。

收銀員:在用戶注冊(cè)遇到問題時(shí),及時(shí)提供幫助

前置條件

企業(yè)經(jīng)營(yíng)者已經(jīng)在系統(tǒng)中配置好了相應(yīng)的會(huì)員折扣

后置條件

注冊(cè)成功,贈(zèng)送積分和折扣券

正常場(chǎng)景

(1)系統(tǒng)顯示“注冊(cè)”選項(xiàng)。

(2)用戶選擇“注冊(cè)”選項(xiàng)。

(3)系統(tǒng)顯示注冊(cè)頁(yè)面,請(qǐng)求用戶輸入必要的信息,例如姓名、電話號(hào)碼、電子郵件等。

(4)用戶輸入所有必要的信息。

(5)系統(tǒng)驗(yàn)證用戶信息的有效性。異常場(chǎng)景處理參考“異常場(chǎng)景一”。

?系統(tǒng)驗(yàn)證用戶的手機(jī)號(hào)碼格式是否正確。

?系統(tǒng)驗(yàn)證用戶的電子郵件格式是否正確。

?系統(tǒng)驗(yàn)證用戶的手機(jī)號(hào)碼是否已被使用。

(6)若用戶提供的信息有效,則系統(tǒng)會(huì)保存該信息并生成唯一的用戶ID。

(7)系統(tǒng)向用戶發(fā)送一條短信驗(yàn)證碼,以驗(yàn)證用戶的手機(jī)號(hào)碼是否正確。異常場(chǎng)景處理參考“異常場(chǎng)景二”。

(8)用戶輸入收到的短信驗(yàn)證碼,確認(rèn)自己的手機(jī)號(hào)碼正確。

(9)在用戶完成驗(yàn)證后,系統(tǒng)發(fā)送歡迎消息,并提供給用戶其賬戶信息和相關(guān)優(yōu)惠信息

異常場(chǎng)景

異常場(chǎng)景一如下。

(1)若用戶提供的信息無效,則系統(tǒng)會(huì)顯示錯(cuò)誤的消息,并請(qǐng)求用戶重新輸入必要的信息。

(2)若用戶輸入無效信息超過5次,則系統(tǒng)會(huì)提示用戶嘗試其他注冊(cè)方式。

異常場(chǎng)景二如下。

(1)若手機(jī)號(hào)碼驗(yàn)證失敗,則系統(tǒng)會(huì)提示用戶檢查其手機(jī)號(hào)碼是否正確。

(2)若用戶無法收到短信驗(yàn)證碼,則系統(tǒng)會(huì)提供重新發(fā)送驗(yàn)證碼的選項(xiàng)。

(3)若用戶仍然無法收到短信驗(yàn)證碼,則系統(tǒng)會(huì)提供其他驗(yàn)證方式,比如郵箱驗(yàn)證等。

(4)若短信驗(yàn)證碼過期,則系統(tǒng)會(huì)提示用戶重新發(fā)送短信驗(yàn)證碼

特殊需求

個(gè)人信息安全,需要對(duì)用戶的手機(jī)號(hào)碼等做脫敏處理后存儲(chǔ)

風(fēng)險(xiǎn)預(yù)估

其他

討論項(xiàng):一個(gè)人是否可以辦理多張會(huì)員卡

以上詳細(xì)用例展示了用例作為一種簡(jiǎn)潔、高效的結(jié)構(gòu)化需求分析工具的價(jià)值。在梳理用戶需求時(shí),我們往往更關(guān)注正常場(chǎng)景,容易忽視異常場(chǎng)景。因此,在完成詳細(xì)用例的編寫后,我們必須重點(diǎn)討論和評(píng)審其是否全面覆蓋了所有異常場(chǎng)景。

3.領(lǐng)域模型設(shè)計(jì)和提煉

完成用例梳理后,很多人可能會(huì)直接進(jìn)入系統(tǒng)設(shè)計(jì)階段。通常的做法是先設(shè)計(jì)數(shù)據(jù)庫(kù)表結(jié)構(gòu)和字段,接著根據(jù)前端界面和交互需求定義接口及其參數(shù),最后依據(jù)流程圖用代碼逐個(gè)實(shí)現(xiàn)這些接口。這種基于數(shù)據(jù)庫(kù)的開發(fā)方式被稱為“事務(wù)腳本模式”。這里我們不深入探討事務(wù)腳本模式的潛在問題,而是著重介紹如何利用前面梳理的用例來設(shè)計(jì)和提煉領(lǐng)域模型。領(lǐng)域模型是對(duì)領(lǐng)域內(nèi)概念類或現(xiàn)實(shí)世界對(duì)象的可視化表示。

首先:領(lǐng)域和子域劃分

通過與業(yè)務(wù)專家溝通,采用歸納法將功能相近的用例歸納并提煉共性。例如,將用戶和收銀員的注冊(cè)會(huì)員、查看會(huì)員信息等功能歸納為會(huì)員管理域;將會(huì)員卡儲(chǔ)值、核銷會(huì)員權(quán)益等功能歸納為會(huì)員卡權(quán)益管理域;將用戶參加促銷活動(dòng)、企業(yè)配置活動(dòng)規(guī)則等功能歸納為營(yíng)銷活動(dòng)管理域。這些領(lǐng)域劃分結(jié)果如圖12所示。

圖12 會(huì)員系統(tǒng)領(lǐng)域劃分圖12 會(huì)員系統(tǒng)領(lǐng)域劃分

對(duì)領(lǐng)域的劃分其實(shí)在與業(yè)務(wù)專家的溝通過程中就能很清晰地判斷出來,通過用例歸納法大多是對(duì)子域進(jìn)行細(xì)化和驗(yàn)證。而對(duì)于各個(gè)領(lǐng)域,我們還可以進(jìn)一步細(xì)化。例如,對(duì)于營(yíng)銷活動(dòng)管理領(lǐng)域,根據(jù)用戶和企業(yè)經(jīng)營(yíng)者職責(zé)的不同,可以將其繼續(xù)劃分為活動(dòng)規(guī)則配置、券與禮品配置、營(yíng)銷活動(dòng)報(bào)表三個(gè)子域,如圖13所示。

圖13營(yíng)銷活動(dòng)管理領(lǐng)域的子域劃分圖13營(yíng)銷活動(dòng)管理領(lǐng)域的子域劃分

其次:尋找領(lǐng)域?qū)ο?/strong>

劃分領(lǐng)域是宏觀層面的業(yè)務(wù)垂直切分,他在DDD中是包含在戰(zhàn)略建模范圍中的,并且領(lǐng)域還可以很好地幫助我們劃分微服務(wù)。但是只劃分好微服務(wù)不是我們的目的,我們希望能更好地開發(fā)出可讀性高、易維護(hù)和易推展的代碼。而面向?qū)ο缶幊趟枷胫校詈诵牡木褪侨绾魏侠淼膭澐謱?duì)領(lǐng)域?qū)ο?,也就是說面向?qū)ο蠓治龅木杈褪菑念I(lǐng)域到重要概念和對(duì)象的分解。

那么如何找到領(lǐng)域中重要的概念和對(duì)象呢?這里主要參考Craig Larman的《UML和模式應(yīng)用》書中的三種方法:

1.重用和修改現(xiàn)有的模型。因?yàn)樵谠S多常見的領(lǐng)域中都存在已發(fā)布的、繪制精細(xì)的領(lǐng)域模型和數(shù)據(jù)模型,比如像RBAC(Role-Based Access Control)權(quán)限管理模型等。這里推薦Martin Fowler的《分析模式》一書,在該書中總結(jié)了很多常用通常的領(lǐng)域模型。

2.使用分類列表方式。該方式有興趣可以去研究學(xué)習(xí)下,這里不過多介紹。

3.通過識(shí)別名詞短語(yǔ)尋找概念類,又稱為用例建模法。該方式是我重點(diǎn)推薦的方式,我們可以通過對(duì)所有詳細(xì)用例的文本進(jìn)行分析,識(shí)別出其中的關(guān)鍵名詞和名詞短語(yǔ),將其作為候選的概念類或?qū)傩?。如下圖所示步驟:

圖14 用例建模法圖14 用例建模法

名詞短語(yǔ)法可以比較容易地找出領(lǐng)域?qū)ο蠛蛯?duì)象之間的關(guān)系,從而提煉出領(lǐng)域模型,具體可以采用以下步驟來提煉領(lǐng)域模型。

  • 從用例集中找出名詞短語(yǔ)。舉例:不同等級(jí)的會(huì)員卡優(yōu)惠不一樣,這句話中的名詞短語(yǔ)有“等級(jí)”和“會(huì)員卡”。
  • 根據(jù)名詞短語(yǔ)梳理領(lǐng)域?qū)ο蠡驅(qū)傩?。舉例:前文中的“會(huì)員卡”可以抽象為領(lǐng)域?qū)ο螅瘛暗燃?jí)”可能就只是會(huì)員卡對(duì)象中的一個(gè)屬性值了。
  • 從用例集中找出動(dòng)詞和形容詞。舉例:一個(gè)人可以辦理多張會(huì)員卡。其中“辦理”為動(dòng)詞。
  • 根據(jù)動(dòng)詞和形容梳理領(lǐng)域?qū)ο笾g的關(guān)系。舉例:前文中提到的動(dòng)詞短語(yǔ)“辦理多張會(huì)員卡”,可以推測(cè)出“會(huì)員”對(duì)象和“會(huì)員卡”之間存在一對(duì)多的關(guān)系。

通過以上步驟,最后我們繪制出的會(huì)員領(lǐng)域模型如下圖所示:

圖14 用例建模法圖14 用例建模法

說到這里,可能會(huì)有人覺得架構(gòu)師只是寫PPT和文檔、講理論,其實(shí)不然。架構(gòu)師不僅要會(huì)畫圖,更要寫代碼,而且代碼要能精準(zhǔn)體現(xiàn)設(shè)計(jì)的領(lǐng)域模型。接下來,讓我們進(jìn)入大家都很熟悉的代碼實(shí)現(xiàn)環(huán)節(jié)。

4.戰(zhàn)略設(shè)計(jì):水平和垂直劃分服務(wù)或模塊

我們需要先把架構(gòu)設(shè)計(jì)做好水平和垂直劃分,對(duì)應(yīng)于DDD中提出的戰(zhàn)略設(shè)計(jì)部分,他是架構(gòu)設(shè)計(jì)中的核心穩(wěn)定的主體結(jié)構(gòu),就像設(shè)計(jì)房屋大廈中的主體結(jié)構(gòu)一樣,一經(jīng)設(shè)計(jì),未來如果需要大變動(dòng),那么將會(huì)付出非常大的代價(jià)的。在軟件的架構(gòu)設(shè)計(jì)中,這個(gè)主體主要包括微服務(wù)如何劃分,各服務(wù)之間的依賴關(guān)系是什么樣的,以及各微服務(wù)內(nèi)部的層次結(jié)構(gòu)是什么樣的。在會(huì)員系統(tǒng)項(xiàng)目中,各微服務(wù)和模塊設(shè)計(jì)如下圖所示:

圖16 會(huì)員系統(tǒng)邏輯架構(gòu)圖圖16 會(huì)員系統(tǒng)邏輯架構(gòu)圖

而微服務(wù)內(nèi)部層次結(jié)構(gòu)按照DDD的四層結(jié)構(gòu)劃分的。分層架構(gòu)的一個(gè)重要原則是每層只能與位于其下方的層發(fā)生耦合。分層架構(gòu)可以簡(jiǎn)單分為兩種,即嚴(yán)格分層架構(gòu)和松散分層架構(gòu)。在嚴(yán)格分層架構(gòu)中,某層只能與位于其直接下方的層發(fā)生耦合,而在松散分層架構(gòu)中,則允許某層與它的任意下方層發(fā)生耦合。我們采用的是DDD的松散分層架構(gòu)圖如下。

圖17 DDD架構(gòu)分層圖17 DDD架構(gòu)分層

其中各層介紹如下:

  • 用戶接口層(User Interface):這是用戶與系統(tǒng)進(jìn)行交互的界面層,是系統(tǒng)的最外層,負(fù)責(zé)處理用戶輸入和展示系統(tǒng)輸出。
  • 應(yīng)用層(Application):負(fù)責(zé)展現(xiàn)層與領(lǐng)域?qū)又g的協(xié)調(diào),協(xié)調(diào)業(yè)務(wù)對(duì)象來執(zhí)行特定的應(yīng)用程序任務(wù)。它不包含業(yè)務(wù)邏輯,所以相對(duì)來說是較“薄”的一層。
  • 領(lǐng)域?qū)樱―omain):負(fù)責(zé)表達(dá)業(yè)務(wù)概念,實(shí)現(xiàn)全部業(yè)務(wù)邏輯并且通過各種校驗(yàn)手段保證業(yè)務(wù)正確性,是最核心關(guān)鍵的部分。而什么是業(yè)務(wù)邏輯呢?它包括業(yè)務(wù)的流程、策略、規(guī)則、狀態(tài)、以及完整性約束等,所以領(lǐng)域?qū)邮禽^“胖”的一層。
  • 基礎(chǔ)設(shè)施層(Infrastructure):這一層是系統(tǒng)的支撐層,提供了系統(tǒng)運(yùn)行所需的基礎(chǔ)服務(wù)和設(shè)施,為其他層的實(shí)現(xiàn)提供技術(shù)支撐。

其對(duì)應(yīng)會(huì)員系統(tǒng)工程代碼結(jié)構(gòu)如下圖所示:

圖18 會(huì)員系統(tǒng)工程結(jié)構(gòu)圖圖18 會(huì)員系統(tǒng)工程結(jié)構(gòu)圖

5.戰(zhàn)術(shù)設(shè)計(jì):使用DDD工具實(shí)現(xiàn)領(lǐng)域模型

在前文中介紹了如何根據(jù)用例來分析和劃分領(lǐng)域和子域,根據(jù)領(lǐng)域和子域創(chuàng)建了微服務(wù)和服務(wù)內(nèi)的模塊,并且提煉了對(duì)應(yīng)領(lǐng)域的領(lǐng)域模型,接下來將根據(jù)核心詳細(xì)用例繼續(xù)設(shè)計(jì)系統(tǒng)內(nèi)部的具體實(shí)現(xiàn)邏輯。根據(jù)詳細(xì)用例,我們可以找到用戶和系統(tǒng)之間的交互關(guān)系。在會(huì)員系統(tǒng)中,根據(jù)會(huì)員注冊(cè)這個(gè)詳細(xì)用例,我們采用UML時(shí)序圖來設(shè)計(jì)用戶和系統(tǒng)之間的交互關(guān)系,如下圖所示:

圖19 會(huì)員注冊(cè)交互圖圖19 會(huì)員注冊(cè)交互圖

在上圖中只反映了用戶與系統(tǒng)之間的交互關(guān)系。因?yàn)橛脩糁魂P(guān)心如何使用系統(tǒng)達(dá)到自己的目的,所以我們?cè)谇捌趦?yōu)先設(shè)計(jì)用戶與系統(tǒng)之間的交互關(guān)系,而且此時(shí)系統(tǒng)實(shí)現(xiàn)對(duì)于用戶來說還是“黑盒”,后續(xù)可以繼續(xù)對(duì)該“黑盒”(新會(huì)員注冊(cè)對(duì)象NewMemberRegister)進(jìn)行完善。在這里,用戶和新會(huì)員注冊(cè)對(duì)象之間的兩次交互對(duì)應(yīng)分層架構(gòu)中的接口層(可能有兩個(gè)接口),而新會(huì)員注冊(cè)對(duì)象對(duì)應(yīng)分層架構(gòu)中的應(yīng)用層。為了實(shí)現(xiàn)用戶與系統(tǒng)之間的兩次交互,在應(yīng)用層的新會(huì)員注冊(cè)對(duì)象中就需要具備對(duì)應(yīng)的業(yè)務(wù)流程,對(duì)于其詳細(xì)的業(yè)務(wù)流程,可以繼續(xù)使用UML流程圖或時(shí)序圖來完善設(shè)計(jì)。這里使用UML時(shí)序圖來完善新用戶注冊(cè)的詳細(xì)業(yè)務(wù)流程,下圖所示為關(guān)于新用戶注冊(cè)的業(yè)務(wù)流程時(shí)序圖。

圖20 會(huì)員注冊(cè)時(shí)序圖

在通過UML時(shí)序圖和流程圖完成“新會(huì)員注冊(cè)”用例對(duì)應(yīng)的系統(tǒng)設(shè)計(jì)后,接下來我們將使用DDD中提供的戰(zhàn)術(shù)設(shè)計(jì)工具來設(shè)計(jì)高內(nèi)聚和低耦合的具體代碼。Eric Evans在他最經(jīng)典的《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì):軟件核心復(fù)雜性應(yīng)對(duì)之道》一書中提供了以下具體的工具來實(shí)現(xiàn)這一目標(biāo)。

  • 實(shí)體(Entity):實(shí)體是一個(gè)不由自身屬性定義而由它自身身份定義的對(duì)象,它是具有狀態(tài)和行為的。實(shí)體對(duì)象具有唯一性并且是可持續(xù)變化的,也就是說在實(shí)體的生命周期內(nèi),無論其如何變化,其仍舊是同一個(gè)實(shí)體。它的唯一性由唯一的身份標(biāo)識(shí)來決定的,而它的可變性也正反映了實(shí)體本身的狀態(tài)和行為。
  • 值對(duì)象(Value Object):只包含元素屬性的不可變對(duì)象。值對(duì)象是將一個(gè)值用對(duì)象的方式來表述,進(jìn)而表達(dá)一個(gè)具體的固定不變的概念。比如某個(gè)地址(Address)對(duì)象,它不用唯一身份標(biāo)識(shí)id來決定它的唯一性,它只用通過固定不變的概念來表示一個(gè)具體的地址就好。
  • 應(yīng)用服務(wù)(Application Service),是用來表達(dá)用戶故事(User Story)和用例(User Case)的主要手段。應(yīng)用層通過應(yīng)用服務(wù)接口來暴露系統(tǒng)的全部功能。在應(yīng)用服務(wù)的實(shí)現(xiàn)中,它負(fù)責(zé)編排和轉(zhuǎn)發(fā),它將要實(shí)現(xiàn)的功能委托給一個(gè)或多個(gè)領(lǐng)域?qū)ο髞韺?shí)現(xiàn),它本身只負(fù)責(zé)處理業(yè)務(wù)用例的執(zhí)行順序以及結(jié)果的拼裝。通過這樣的方式能很好的隱藏領(lǐng)域?qū)拥膹?fù)雜性及其內(nèi)部實(shí)現(xiàn)機(jī)制。應(yīng)用層除了定義應(yīng)用服務(wù)之外,在該層我們可以進(jìn)行安全認(rèn)證,權(quán)限校驗(yàn),持久化事務(wù)控制,調(diào)用外部系統(tǒng)或者向其他系統(tǒng)發(fā)送事件消息等。另外,應(yīng)用層作為展示層與領(lǐng)域?qū)拥臉蛄?,展示層使用VO(視圖模型)進(jìn)行界面展示,與應(yīng)用層通過DTO(數(shù)據(jù)傳輸對(duì)象)進(jìn)行數(shù)據(jù)交互,從而達(dá)到展示層與DO(領(lǐng)域?qū)ο螅┙怦畹哪康摹?/li>
  • 領(lǐng)域服務(wù)(Domain Service),當(dāng)領(lǐng)域中的某個(gè)操作過程或轉(zhuǎn)換過程不是實(shí)體或值對(duì)象的職責(zé)時(shí)(比如跨多個(gè)領(lǐng)域?qū)ο蟮牟僮鳎?,我們便?yīng)該將該操作放在一個(gè)單獨(dú)的接口中,即領(lǐng)域服務(wù)。領(lǐng)域服務(wù)是用來協(xié)調(diào)領(lǐng)域?qū)ο笸瓿赡硞€(gè)操作,用來處理業(yè)務(wù)邏輯的,它本身是一個(gè)行為,所以是無狀態(tài)的,狀態(tài)由領(lǐng)域?qū)ο螅ň哂袪顟B(tài)和行為)保存。
  • 模塊(Module):是指提供特定功能的相對(duì)獨(dú)立的單元,也就是對(duì)功能的分解和組合。模塊的用途是通過分解領(lǐng)域模型為不同的模塊,以降低領(lǐng)域模型的復(fù)雜性,提高領(lǐng)域模型的可讀性。
  • 聚合(Aggregate):聚合是由聚合根(ROOT ENTITY) 綁定在一起的對(duì)象的集合,是領(lǐng)域?qū)ο蟮娘@示分組,來表達(dá)整體的概念(也可以是單一的領(lǐng)域?qū)ο螅淖谥际菫榱酥С诸I(lǐng)域模型的行為和不變性,同時(shí)充當(dāng)一致性和事務(wù)性邊界。聚合根通過禁止外部對(duì)象對(duì)其成員的引用來保證在聚合內(nèi)進(jìn)行的更改是一致性的,所以它的難點(diǎn)一般在于一致性的維護(hù)上:聚合內(nèi)實(shí)現(xiàn)事務(wù)一致性,聚合外實(shí)現(xiàn)最終一致性。
  • 工廠(Factory):工廠是用來封裝對(duì)象創(chuàng)建所必需的知識(shí),它們對(duì)創(chuàng)建聚合特別有用。一個(gè)對(duì)象的創(chuàng)建可能是它自身的主要操作,但是復(fù)雜的組裝操作不應(yīng)該成為被創(chuàng)建對(duì)象的職責(zé),因?yàn)榻M裝這樣的職責(zé)會(huì)產(chǎn)生笨拙的設(shè)計(jì),也很難讓人理解。而工廠可以幫助封裝復(fù)雜對(duì)象的創(chuàng)建過程,并且當(dāng)聚合根建立時(shí),所有聚合包含的對(duì)象也隨之建立了,整個(gè)過程是又是原子化的。
  • 倉(cāng)儲(chǔ)(Repository):倉(cāng)儲(chǔ)是對(duì)聚合的管理,它介于領(lǐng)域模型和數(shù)據(jù)模型之間,主要用于聚合的持久化和檢索,同時(shí)對(duì)領(lǐng)域模型和數(shù)據(jù)模型進(jìn)行了隔離,以便我們關(guān)注于領(lǐng)域模型而不需要考慮如何進(jìn)行持久化。

接下來使用這些領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)中提供的戰(zhàn)術(shù)建模工具來完成會(huì)員系統(tǒng)領(lǐng)域模型的代碼開發(fā)。

首先創(chuàng)建微服務(wù)的項(xiàng)目工程,然后在項(xiàng)目工程的領(lǐng)域?qū)觿?chuàng)建會(huì)員領(lǐng)域模型的領(lǐng)域?qū)ο?,最后將各個(gè)領(lǐng)域?qū)ο蟀凑詹煌穆氊?zé)分配到對(duì)應(yīng)的微服務(wù)中,如下圖所示。

圖21 會(huì)員系統(tǒng)工程結(jié)構(gòu)圖圖21 會(huì)員系統(tǒng)工程結(jié)構(gòu)圖

在上圖中創(chuàng)建了會(huì)員(Member)對(duì)象與會(huì)員卡(Card)對(duì)象等領(lǐng)域?qū)ο?,并且?huì)員對(duì)象與會(huì)員卡對(duì)象為一對(duì)多的關(guān)系。在具體的實(shí)現(xiàn)過程中,代碼必須真實(shí)地反映領(lǐng)域模型,若在代碼實(shí)現(xiàn)中發(fā)現(xiàn)之前設(shè)計(jì)的領(lǐng)域模型不合理,就需要及時(shí)地調(diào)整設(shè)計(jì)中的模型結(jié)構(gòu),盡量保證模型和代碼始終一致。在創(chuàng)建領(lǐng)域?qū)嶓w對(duì)象后,我們還需要在應(yīng)用層實(shí)現(xiàn)業(yè)務(wù)流程,并調(diào)用相應(yīng)的領(lǐng)域?qū)ο髞硗瓿蓸I(yè)務(wù)邏輯處理。會(huì)員系統(tǒng)案例中會(huì)員注冊(cè)用例場(chǎng)景的業(yè)務(wù)流程編排邏輯代碼示例如下:

public class NewMemberRegister 
{
 
@Resource
private RegisterRepository registerRepo;
/**
* 
會(huì)員注冊(cè)業(yè)務(wù)流程實(shí)現(xiàn)
* @param memberInfoDTO
注冊(cè)信息
* @return
注冊(cè)結(jié)果
*/
public RegisterResultDTO registerMember(MemberInfoDTO memberInfoDTO)
{
RegisterResultDTO resultDTO = null;
/
/ 
第
1
步,判斷用戶是否已是會(huì)員
boolean isExist = registerRepo.isMemberExist(memberInfoDTO.getPhone());
if(isExist)
{
resultDTO = new RegisterResultDTO(RegisterConst.MEMBER_EXIST,RegisterConst.MEMBER_EXIST_MSG);
return resultDTO;
}
// 
第
2
步,創(chuàng)建會(huì)員聚合對(duì)象
Member member = MemberFactory.createMember(memberInfoDTO);
// 
第
3
步,執(zhí)行注冊(cè)會(huì)員業(yè)務(wù)邏輯。例如,根據(jù)儲(chǔ)值金額的不同,開通不同等級(jí)的會(huì)員卡
boolean isSuccess = member.applyMemberCard(memberInfoDTO.getMoney());
if(!isSuccess)
{
resultDTO = new RegisterResultDTO(RegisterConst.MEMBER_APPLY,RegisterConst.MEMBER_APPLY_MSG);
return resultDTO;
}
// 
第
4
步,持久化聚合根數(shù)據(jù)
boolean isSave = registerRepo.saveMember(member);
if(!isSave)
{
resultDTO = new RegisterResultDTO(RegisterConst.MEMBER_SAVE_ERROR,RegisterConst.MEMBER_SAVE_MSG);
return resultDTO;
}
// 
第
5
步,返回會(huì)員卡辦理成功的消息
resultDTO = assembleRegisterResultDTO(member);
return resultDTO;
}
private RegisterResultDTO assembleRegisterResultDTO(Member member)
{
// 
把領(lǐng)域?qū)ο筠D(zhuǎn)換為傳輸對(duì)象
DTO
,此處省略代碼
return resultDTO;
}
}

該實(shí)現(xiàn)代碼對(duì)應(yīng)時(shí)序設(shè)計(jì)圖(圖20),主要在應(yīng)用層的 NewMemberRegister 對(duì)象的registerMember()方法中實(shí)現(xiàn)會(huì)員注冊(cè)的業(yè)務(wù)流程編排。對(duì)領(lǐng)域?qū)ο蟮膭?chuàng)建則由具體的工廠類實(shí)現(xiàn),因?yàn)闀?huì)員實(shí)體和會(huì)員卡實(shí)體為一對(duì)多的關(guān)系,所以這里使用了單獨(dú)的工廠類(MemberFactory)來實(shí)現(xiàn),同時(shí),會(huì)員實(shí)體充當(dāng)聚合根對(duì)象。代碼示例如下:

public class MemberFactory 
{
 
/**
 
* 
工廠方法,默認(rèn)綁定一張會(huì)員卡,若創(chuàng)建多張會(huì)員卡,則請(qǐng)使用
createMoreMember()
工廠方法
 
* @param memberInfoDTO
 
* @return Member
對(duì)象
 
*/
 
public static Member createMember(MemberInfoDTO memberInfoDTO)
{
 
// 
創(chuàng)建
Member
對(duì)象,并為其賦值
 
Member member = new Member();
 
// 
省略
Member
賦值代碼
 
Card card = new Card();
 
// 
省略
Card
賦值代碼
 
// 
綁定一張會(huì)員卡
 
member.bindCard(card);
 
return member;
 
}
}

具體的會(huì)員注冊(cè)業(yè)務(wù)邏輯主要被封裝在Member和Card實(shí)體中。例如,在新用戶注冊(cè)場(chǎng)景中,根據(jù)儲(chǔ)值金額開通不同等級(jí)的會(huì)員卡并為之賦予相應(yīng)權(quán)益的業(yè)務(wù)邏輯,對(duì)應(yīng)的實(shí)現(xiàn)代碼被封裝在Member實(shí)體中。代碼示例如下:

public class Member 
{
 
/*
電話號(hào)碼
*
/
private String phone;
/*
姓名
*/
private String name;
/*
會(huì)員卡列表
*
/
private List<Card> cards;
/**
* 
新會(huì)員注冊(cè)業(yè)務(wù)邏輯,根據(jù)用戶儲(chǔ)值金額的不同,開通不同等級(jí)的會(huì)員卡
* @param money
* @return
*/
public boolean applyMemberCard(int money)
{
switch (money)
{
case CardLevelConst.LEVEL1_MONEY :
// 
普通卡,設(shè)置普通卡權(quán)益,例如打
9
折
// 
此處省略代碼
break;
case CardLevelConst.LEVEL2_MONEY:
// 
金卡,設(shè)置金卡權(quán)益。例如,打
88
折,同時(shí)贈(zèng)送一張
10
元代金券
// 
此處省略代碼
break;
case CardLevelConst.LEVEL3_MONEY:
// 
黑金卡,設(shè)置黑金卡權(quán)益。例如,打
6
折,同時(shí)贈(zèng)送一張
50
元代金券
// 
此處省略代碼
break;
default:
// 
不符合辦卡條件
return false;
}
return true;
}
/**
*
為會(huì)員創(chuàng)建一張會(huì)員卡片,并將其與會(huì)員綁定
* @param card
*/
public void bindCard(Card card)
{
if(CollectionUtils.isEmpty(cards))
{
cards = new ArrayList<>();
}
cards.add(card);
}
}

對(duì)實(shí)體對(duì)象的持久化操作,則采用RegisterRepository實(shí)現(xiàn),從而使領(lǐng)域?qū)ο蠛蛿?shù)據(jù)庫(kù)操作解耦,也保證了聚合根內(nèi)對(duì)象的數(shù)據(jù)一致性。代碼如下:

public class RegisterRepositoryImpl implements RegisterRepository 
{
 
private MemberMapper memberMapper; // MyBatis
的
mapper
對(duì)象
private CardMapper cardMapper;
// MyBatis
的
mapper
對(duì)象
@Override
public boolean isMemberExist(String phone) 
{
return memberMapper.findByPhone(phone);
}
@Override
public boolean saveMember(Member member) 
{
MemberPO memberPO = assembleMemberPO(member);
List<CardPO> cardPOList = assembleCardPO(member.getCards());
// 
在同一個(gè)事務(wù)中保存
MemberPO
和
CardPO
數(shù)據(jù)
memberMapper.save(memberPO);
cardMapper.saveAll(cardPOList);
return false;
}
private MemberPO assembleMemberPO(Member member)
{
// 
該方法把領(lǐng)域?qū)ο筠D(zhuǎn)換為數(shù)據(jù)庫(kù)存儲(chǔ)對(duì)象,此處省略代碼
}
private List<CardPO> assembleCardPO(List<Card> cards)
{
// 
該方法把領(lǐng)域?qū)ο筠D(zhuǎn)換為數(shù)據(jù)庫(kù)存儲(chǔ)對(duì)象,此處省略代碼
}
}

至此,我們利用 DDD 工具實(shí)現(xiàn)了會(huì)員系統(tǒng)中的會(huì)員注冊(cè)用例。通過這種方式編寫的代碼,不僅整潔清晰,完整體現(xiàn)了設(shè)計(jì)模型,還能追溯到對(duì)應(yīng)的需求分析,從而確保了實(shí)現(xiàn)與設(shè)計(jì)的一致性。

整潔架構(gòu)實(shí)踐總結(jié)

本文在對(duì)歷史遺留系統(tǒng)問題深入分析后,找到問題的本質(zhì)解,制定切實(shí)有效的解決方案。通過運(yùn)用整潔架構(gòu)設(shè)計(jì)理念,對(duì)歷史遺留系統(tǒng)進(jìn)行全面的模型分析和架構(gòu)重新設(shè)計(jì),并借助DDD戰(zhàn)術(shù)工具實(shí)現(xiàn)代碼開發(fā)。在此過程中,整個(gè)團(tuán)隊(duì)嚴(yán)格保持統(tǒng)一的代碼風(fēng)格,從而使歷史遺留系統(tǒng)煥然一新,變得整潔、清晰且易于維護(hù)。這不僅顯著提升了系統(tǒng)的可讀性和可擴(kuò)展性,還為未來的功能開發(fā)和系統(tǒng)升級(jí)奠定了堅(jiān)實(shí)基礎(chǔ)。

責(zé)任編輯:武曉燕 來源: 君哥聊技術(shù)
相關(guān)推薦

2024-06-19 09:38:05

2019-03-13 09:27:57

宕機(jī)Kafka數(shù)據(jù)

2021-04-21 14:56:28

負(fù)載均衡高并發(fā)優(yōu)化技術(shù)架構(gòu)

2021-05-19 20:41:23

日志檢索系統(tǒng)

2018-01-09 09:45:02

秒級(jí)監(jiān)控阿里

2020-01-13 08:43:20

Elasticsear分布式搜索

2020-09-01 07:49:14

JVM流量系統(tǒng)

2009-07-06 15:05:23

2019-12-25 10:17:53

騰訊Elasticsear開源

2019-10-25 09:28:12

算法設(shè)計(jì)操作系統(tǒng)

2020-10-22 15:55:06

數(shù)據(jù)分析架構(gòu)索引

2022-05-19 09:31:50

Kafka 集群數(shù)據(jù)存儲(chǔ)服務(wù)端

2019-12-25 09:10:44

技術(shù)研發(fā)指標(biāo)

2018-05-21 09:15:06

Redis美團(tuán)點(diǎn)評(píng)數(shù)據(jù)庫(kù)運(yùn)維

2020-12-31 07:34:04

Redis數(shù)據(jù)宕機(jī)

2023-09-07 20:31:48

外灘大會(huì)螞蟻集團(tuán)圖學(xué)習(xí)系統(tǒng)

2018-09-12 15:21:05

云宕機(jī)云計(jì)算數(shù)據(jù)中心

2018-05-28 08:20:12

服務(wù)器Redis優(yōu)化

2010-07-05 16:15:41

流量控制

2016-11-23 12:55:09

京東活動(dòng)系統(tǒng)流量
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)