系統(tǒng)整容紀:責任鏈設計模式的應用實戰(zhàn)
本文通過介紹使用責任鏈設計模式的背景和經(jīng)歷,來使得讀者加深對于此設計模式的印象,甚至受到一定的啟發(fā)來對自己當下所參與、所負責的項目進行“整容”,從而提升系統(tǒng)的“美感”。分享工作中的點點滴滴。
一、背景
在下所負責的系統(tǒng)中有這么一個模塊,分區(qū)模塊,直接看這個詞的話相信很多人都會疑惑甚至是誤解,其實其真正的含義就是“路由”,接下來我簡單描述一下何謂“路由”。
相信大家都有過網(wǎng)上購物的經(jīng)驗,每當我們下完訂單后,我們都能隨時隨地的查看訂單的物流跟蹤狀態(tài),而上述的“路由”概念就是指:訂單從A地到B地的運輸路由線路,例如訂單order1要從A運輸?shù)侥康牡谾,其可以從A->B->D->F,也可以從A->D->F,至于具體應該走哪條線路,是靠系統(tǒng)中配置的路由以及對應的匹配規(guī)則進行篩選出來的。
很長的一段時間里,系統(tǒng)里的路由配置和規(guī)則都是靜態(tài)的(所謂靜態(tài)就是提前配置好并且?guī)缀跏枪潭ú蛔兊模@種做法的弊端很明顯,就是成本無法控制,就如同上述所講的例子,運輸路線明明有機會可以減少甚至是可以直送(A地到F地的貨物明明可以裝滿N車,卻也不得不按照系統(tǒng)中制定的路線進行運輸),但是卻被系統(tǒng)中定死的路由規(guī)則所限只能多走一些道路,提高了人力作業(yè)和運輸?shù)某杀尽?/p>
基于此有位大牛發(fā)現(xiàn)了降本增效的商機:就是讓這塊路由線路和規(guī)則動起來,使得系統(tǒng)能夠更靈活的兼容上述情況,達到資源的極限利用,舉個例子:比如有很多訂單都是從A要送達目的地F的,系統(tǒng)中靜態(tài)線路配置的只有A->B->D->F,但是經(jīng)過系統(tǒng)監(jiān)測計算發(fā)現(xiàn)從A到F的貨物量可以裝滿兩車,那此時為這些訂單臨時生成一條新的線路從A直達F,同時在A場地進行收貨、發(fā)貨等凡是涉及到路由線路匹配的實操環(huán)節(jié)時,均兼容上此臨時路由的場景,這樣就能在不改變用戶習慣的情況下將整體成本給降下來,并且運輸?shù)侥康牡氐男室驳玫搅舜蟠蟮奶岣摺?/p>
這位大牛提出的方案很好,得到了大家的廣泛認可和好評,于是在立項后進入了轟轟烈烈的開發(fā)階段,而在下有幸被委以重任來主導完成此項目的開發(fā)交付。
值得一提的是,路由線路這塊的改動貫穿了訂單的整個實操流程以及一些邊邊角角的輔助查詢、統(tǒng)計報表等功能,場景涉及眾多,所以壓力還是蠻大的,雖然期間也確實走了不少彎路,但最終的結(jié)果是好的,甚至在之后的又來了幾個類似變動路由線路的需求,但是基于此次改造后,我們這邊都能進行輕松應對,這個在文章后續(xù)的效果中會體現(xiàn)一下。
說了這么一大堆,有沒有覺得其實都是廢話的感覺,哈哈,確實有點啰嗦,接下來讓我們來夢回路由,再現(xiàn)一下整個稀碎而又有成就感的改造過程。
二、思路和方法
后續(xù)文章中提到的分區(qū)是為路由匹配規(guī)則的含義
首先來看一張簡易的實操流程示意圖:
在每個場地的實際操作的作業(yè)流程中都會涉及到分區(qū)匹配規(guī)則的情況,同時在一些輔助實操作業(yè)的查詢功能或報表功能中也會涉及到路由匹配規(guī)則,所以一旦分區(qū)匹配規(guī)則要做變動,那么就會面臨如下痛點:
?首先,在業(yè)務層面上就會幾乎貫穿整個流程,無論是評估、開發(fā)、測試等環(huán)節(jié)都會面臨工作量巨大的問題
?再次,從系統(tǒng)層面上來看,這部分的代碼現(xiàn)狀也非常不友好,主要體現(xiàn)在:
?分區(qū)匹配核心業(yè)務規(guī)則一致,但是代碼寫法不一,并且分布散亂,讓人難以閱讀與維護,同時伴隨場景遺漏的風險
?分區(qū)匹配功能目前都是各個實例自行編寫并且耦合在各個使用場景中,不具備擴展性,一旦規(guī)則變化,改動成本非常高
基于上述痛點,在下決定下定決心將此模塊進行重構(gòu)整治一番,一來通過此次挑戰(zhàn)來提高自我、二來也為以后此塊規(guī)則再次變更的可能做好鋪墊,那么具體應該怎么做才能解決上述所提到的痛點呢?我是這么做的。
1.業(yè)務層面上做好充分的評估:體現(xiàn)在開發(fā)的詳細設計上(這里由于在下對于業(yè)務規(guī)則非常熟,所以算是是本人的優(yōu)勢拉哈哈),像場景梳理啊、修改方案、甚至在設計中下沉到了具體功能(按鈕點擊啊、錄入框輸入后的回車觸發(fā)啊等等這些細節(jié))的修改邏輯方案以及代碼位置,以便讓所有參與開發(fā)人員以此為指導手冊進行快速開發(fā),測試人員以此為指導進行用例編寫、產(chǎn)品人員以此為字典加深其自身對于此塊業(yè)務的理解等等。聽起來是不是很牛X,哇哈哈,這里有點吹噓拉,本文中不著重介紹這里,主要介紹設計模式哈,咱們緩緩繼續(xù)往下看。
2.這里是我們的重頭戲哈,系統(tǒng)層面上主要體現(xiàn)在代碼上,畢竟說的再好,落實不到代碼,看不到效果都是白搭嘛,何況咱又是職位,好了,廢話不多說,繼續(xù)看我表演,哈哈
?首先將分區(qū)匹配核心模塊進行統(tǒng)一的收口,在系統(tǒng)中只保留一個實例進行服務提供,既能解決代碼分散維護成本高的問題、又能避免場景遺漏的風險。
看到這里或許會有人說,你這個會不會帶來另外一個問題?。弘m然場景不會遺漏了,達到了一處代碼變動,處處場景都會生效,但是會不會改動了某些場景原有的功能特性?能想到這里的同學,確實很細心哈,如果是硬性收口的話的確是會產(chǎn)生這個新問題,所以統(tǒng)一收口這塊一定要支持擴展,預留好鉤子便于支持各個場景的差異化處理。這么說比較抽象,舉個例子:比如通用場景規(guī)則是所有單子都按照系統(tǒng)既定配置的分區(qū)規(guī)則進行運輸,但是現(xiàn)在有一些商家開通了一些快速送到目的地的服務,那對于這些商家的單子就不能用現(xiàn)有的通用規(guī)則進行分區(qū)匹配運輸了,要按照新的規(guī)則進行分區(qū)匹配從而達到快速運輸?shù)哪康摹_@就是里邊的差異化。
?復用現(xiàn)有的數(shù)據(jù)結(jié)構(gòu),增加分區(qū)類型,并調(diào)整對應的sql和service服務(這里不是本篇重點就不展開講了),在兼容現(xiàn)有生產(chǎn)邏輯的基礎(chǔ)上支持分區(qū)規(guī)則的擴展
?結(jié)合設計模式的思想調(diào)整分區(qū)匹配規(guī)則的代碼結(jié)構(gòu):采用責任鏈模式(這里是本篇文章的重點內(nèi)容)
那么,到底什么是責任鏈模式呢?
大牛給出的定義:使多個對象都有機會處理請求,從而避免了請求的發(fā)送者和接受者之間的耦合關(guān)系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理它為止。
結(jié)合著現(xiàn)有系統(tǒng)的實際業(yè)務來講講我是如何進行套用的:開篇背景中已經(jīng)介紹過現(xiàn)有的分區(qū)匹配規(guī)則為靜態(tài)分區(qū)匹配(如某某業(yè)務點到點、某某業(yè)務點到范圍區(qū)域等等,具體業(yè)務細則就不展開講了,只要知道這里邊的匹配規(guī)則一大堆即可),現(xiàn)在要新增一種規(guī)則支持動態(tài)(也是各種匹配,就不展開講了),這里我把每一種分區(qū)規(guī)則都定義為一種分區(qū)類型,而每一個分區(qū)類型都定義為一個分區(qū)節(jié)點,將這些節(jié)點穿成一條鏈,讓每個請求都在這條鏈中找到匹配自己的線路進行運輸。
由于業(yè)務細則比較敏感,文章中就不具體透露了,不影響重點設計模式的應用理解
結(jié)合定義以及上述分析,那么實際情況到底適不適合使用責任鏈設計模式呢?在下認為只要能夠解決上述痛點并且總體的利大于弊,那么就是適用的。首先使用此模式改造后的優(yōu)點如下:
?將請求和處理分開了(從與業(yè)務實操的耦合中解脫出來,完全不用關(guān)心請求是怎么來的,只專注于分區(qū)規(guī)則的匹配)
?提高了系統(tǒng)的靈活性和擴展性(再有新的規(guī)則,只需要增加節(jié)點即可)
當然了,此模式也有一定的缺點:當責任鏈比較長的時候,由于每個請求都會遍歷整個鏈條,可能會有性能的問題,同時對于不理解業(yè)務的同學調(diào)試起來也會感覺比較復雜。
整體看下來,優(yōu)點是解決了我們當下的痛點,并且利于后續(xù)的擴展,而缺點中的性能部分可通過結(jié)合模板模式中預留的鉤子函數(shù)(如果當下請求不適合于當下分區(qū)節(jié)點規(guī)則則跳過)拉最大限度的降低性能問題的影響,同時基于開發(fā)人員了解業(yè)務也是應當?shù)?。那這樣看來總體還是利大于弊的。
說了這么多我們先來看看改造前后的分區(qū)模塊的簡單對比示意圖吧!
雖然圖非常簡陋,但是其對比含義還是很明顯的,改造前:分區(qū)匹配和業(yè)務處理是耦合在一塊的;改造后:分區(qū)匹配是一條鏈并且里邊沒有業(yè)務邏輯處理,從耦合中解脫了出來,也支持擴展。看到這里會有人疑惑:上文中不是說只新增了一個動態(tài)匹配規(guī)則嗎,怎么鏈中有這么多節(jié)點,而且還是兩條鏈。這里我稍作解釋下:當下的兩條鏈是經(jīng)歷過許多需求版本變更的,當下看起來區(qū)別已是不大,主要是因為其提供的來源業(yè)務指定場景不同抽象出來的兩條鏈,互不干擾各自運行,而里邊多處本文介紹的那些節(jié)點也是后來新增的,直至當下還在系統(tǒng)中使用的節(jié)點規(guī)則(這也就是文章開篇我提的:萬一后邊還有規(guī)則變更呢。果然還是機智如我,“預言”應驗了,在效果中我會講出這里支持擴展對于縮短工期的重要性)
三、實踐過程
相信有不少讀者會發(fā)現(xiàn),上面又是說統(tǒng)一收口、又是說結(jié)合模板模式最大程度規(guī)避性能影響,那你這個責任鏈一種設計模式是支持不了的吧。
沒錯,你說對了,大聰明,確實只是使用單一的責任鏈設計模式遠遠達不到上文中所說的效果,這里我們也確實將工廠、模板、責任鏈模式進行了結(jié)合使用,工廠用來獲取鏈條的bean,模板用來設置通用方法、方法間的調(diào)用以及預留給子類實現(xiàn)的開關(guān)、前后置處理、差異化等方法,責任鏈用來組合各個節(jié)點,這里簡單抽幾個節(jié)點將類圖展示出來如下:
這段就比較簡單了,畢竟就是擼碼,上面的類圖幾乎代碼中的實踐應用了,也是代碼中的核心部分,而在實際調(diào)用中均是通過工廠來獲取bean鏈條進行具體分區(qū)匹配的。
四、對實踐過程的思考和對效果的評價
改造過后的成果主要體現(xiàn)在后續(xù)的擴展和維護,就如文章開篇提到的一旦分區(qū)匹配規(guī)則要做變動就會面臨兩個痛點,最直接的體現(xiàn)就是在工期上面,第一次接此塊的變動新增路由規(guī)則需求時,整體實際用的工期就研發(fā)側(cè)來說是45天,就更別說一旦遇到BUG,那測試工期也就沒保障了(對應上圖節(jié)點中的動態(tài)節(jié)點)。
但是后來沒多久就又再次增加匹配規(guī)則的新需求(對應上圖節(jié)點中的到倉拼車節(jié)點),同類的需求,工期縮短近半,優(yōu)45天降低到了27天,這里還包括此需求中的其他非分區(qū)模塊的改造,當然了第一版的改造確實有些完美的地方,經(jīng)過這次需求由進行了優(yōu)化。
接下來的這兩個就真正體現(xiàn)到了什么叫做工期消失術(shù),一個是首板分區(qū)規(guī)則需求,一個是最近B網(wǎng)融合需求中的直發(fā)分區(qū)規(guī)則:
?直發(fā)分區(qū)工期2天
?首板分區(qū)工期就1天
說實話,我在沒回看這些數(shù)據(jù)的時候也沒想到有這么大的效果,現(xiàn)在回頭一看我也是驚呆了,誰能想到一個設計模式的應用竟能將工期由45天縮短至了1天,這太不可思議了。
當然了其中也還有一些可以改進升級的地方,目前責任鏈節(jié)點的裝配都是手動指定的,可改動為自動裝配(我在另個業(yè)務場景中的改造中已經(jīng)實現(xiàn)過了,這里也會進行同步改造),再有一個就是要控制節(jié)點的數(shù)量,如果數(shù)量過大則可能需要考慮兼容方案了。
俗話說滴水穿石非一日之功,冰凍三尺非一日之寒,追求強大的工具、新穎的技術(shù)固然可行,但是也不要忘了日常工作中的一點一滴的小改動,短時之間可能看不出什么,一旦量變引起了質(zhì)變,我相信那結(jié)果將是非常可觀亮眼的。