背景
在日常業(yè)務的研發(fā)過程中,由于需求的緊迫性,往往留給研發(fā)團隊的時間相對有限。這種情況下,為了確保業(yè)務盡快上線,不得不在一定程度上犧牲設計的嚴謹性和技術(shù)的前瞻性,雖然短期內(nèi)看似解決了燃眉之急,但長此以往,卻逐漸累積了大量的技術(shù)債務。
技術(shù)債,又稱設計債或代碼債,是指在軟件開發(fā)過程中為了追求快速交付而采取的非最佳實現(xiàn)方式所帶來的后續(xù)成本。這種“債務”可能源于使用了臨時性的解決方案、忽略了代碼質(zhì)量、或沒有遵循良好的設計原則和實踐。
隨著技術(shù)復雜度的逐步提升,系統(tǒng)的可維護性不可避免地受到侵蝕。這種演變并非線性的遞增,而是呈現(xiàn)出指數(shù)級的增長趨勢,最終會導致需求吞吐、交付質(zhì)量雙雙下降。
為進一步提升服務品質(zhì)和用戶體驗,斗拱進件板塊決定啟動客戶全旅程項目,實施客戶體驗的深度改造。但是在整個項目的研發(fā)實施過程中,工程師們發(fā)現(xiàn)由于業(yè)務邏輯與系統(tǒng)模塊的復雜與緊耦合,在開發(fā)的時候碰到了較多的困難與挑戰(zhàn),包括研發(fā)代碼的周期與測試的質(zhì)量等等,雖然最終有驚無險,順利完成交付,但是我們發(fā)現(xiàn),進件系統(tǒng)的維護與擴展已進入一個新的難度等級,必須啟動戰(zhàn)略性重構(gòu)升級計劃,為業(yè)務可持續(xù)發(fā)展夯實基礎。
問題分析
經(jīng)過多年的持續(xù)迭代投入,斗拱的整套進件體系功能越來越強大,代碼量也越來越多,一次完整的進件,要由大量的微服務共同協(xié)作完成。從應用架構(gòu)層面,進件系統(tǒng)可分為:以領域?qū)訛閺陀煤诵模怨δ軐訛榻桓吨匦模越换訛轶w驗迭代中心,這樣的一套三層架構(gòu)體系:
● 交互層:包含API、WEB頁、APP等交互邏輯與服務;
● 功能層:組合領域?qū)釉咏涌冢纬蓸I(yè)務功能向交互層輸出;
● 領域?qū)樱禾峁┏橄筮^后的原子服務能力,場景關(guān)聯(lián)性弱;
該分層架構(gòu)在人員配備充足,業(yè)務及團隊穩(wěn)定的情況下,是能夠充分發(fā)揮其設計效能的。然而,隨著斗拱業(yè)務規(guī)模的持續(xù)擴大,面對多產(chǎn)品線高并發(fā)迭代的工程挑戰(zhàn)與客戶需求的爆發(fā)式增長態(tài)勢,研發(fā)資源的配置根本滿足不了業(yè)務的吞吐速率要求。為確保重要業(yè)務目標的順利交付,部分團隊不得不采用敏捷交付機制,在架構(gòu)設計層面進行必要的動態(tài)平衡——通過建立架構(gòu)治理框架下的技術(shù)決策機制,允許在關(guān)鍵路徑上實施階段性靈活調(diào)整,同時為體系化重構(gòu)預留技術(shù)窗口。
其次,在推進支付平臺化建設的戰(zhàn)略進程中,由于缺乏成熟的范式可供參考,我們特地組建了專門的技術(shù)委員會。面對各行各業(yè)的多樣化需求、各類用戶角色以及紛繁復雜的場景故事,如何在一套平臺上實現(xiàn)這些功能,成為了委員會頻繁討論的焦點:是沉淀復用還是個性化定制?是僅考慮某一端還是全面支持所有端?是做到交互層、功能層還是領域?qū)樱扛鞣接^點不一,但都有其獨到之處。
長期運作下來,進件系統(tǒng)各層級,都在大規(guī)模的并行迭代,以致于:
第一,用戶交互層與功能層之間,出現(xiàn)了比較多的冗余邏輯,加劇了系統(tǒng)的復雜度。
第二,功能層與領域?qū)又g,也存在相似的重復代碼現(xiàn)象,技術(shù)債務累積。
再者,功能層主要由管理服務和查詢服務兩個團隊負責。其中,管理服務涵蓋了客戶體驗的方方面面,包括但不限于進件材料與流程、業(yè)務配置與開通、協(xié)議簽署,以及一系列配套的運營支撐功能。這些功能不僅要滿足標準化的業(yè)務需求,還需兼顧用戶的功能體驗和重點客戶的個性化定制。隨著時間的推移,管理服務逐漸變得愈加龐大復雜,后續(xù)的維護與擴展難度也隨之逐步增加。
最終,管理服務成為了交付瓶頸,需求越積越多。當新增客戶全旅程這樣一個非常大的業(yè)務能力時,我們來到了復雜度指數(shù)上升的邊沿。
重構(gòu)思路
重構(gòu)(Refactoring),即還技術(shù)債,是指在不改變軟件外部行為的前提下,對軟件內(nèi)部結(jié)構(gòu)進行優(yōu)化和調(diào)整的過程。其主要目的是提高代碼的可讀性、可維護性和可擴展性,從而降低后續(xù)開發(fā)和維護的成本,提升交付質(zhì)量和效率。
論及重構(gòu),領域驅(qū)動設計(DDD)無疑是最先映入腦海的方法論。沒錯,我們也正是借助DDD的戰(zhàn)略設計,對子域進行了重新劃分,形成商戶、審核、協(xié)議等子域,通過DDD的戰(zhàn)術(shù)設計,將一些共通的邏輯提煉出來,形成業(yè)務權(quán)限、計費等模塊。
領域驅(qū)動設計(Domain-Driven Design,簡稱DDD)是一種軟件設計方法論,它強調(diào)以業(yè)務領域為核心進行軟件設計,通過創(chuàng)建豐富的領域模型來反映業(yè)務邏輯,從而實現(xiàn)業(yè)務和技術(shù)的統(tǒng)一。
整個重構(gòu)落地也是非常復雜,為了確保業(yè)務連續(xù)性不受影響,我們通過系統(tǒng)化、工程化的手段,分批次啟動項目實施。整體分為兩大階段:
階段一:領域拆分與沉淀
1、原斗拱進件板塊,按照DDD的設計思想,重新進行領域的劃分;
2、按照新的劃分(子域A、子域B...),將交互層、功能層的邏輯沉淀到各個領域;
3、擴充領域職能,使其不僅提供內(nèi)部服務,同時提供對外的API及應用組件;
核心思路:把原先按功能職責拆分的橫向切分架構(gòu),重構(gòu)為按領域職責拆分的縱向切分架構(gòu),如下圖所示:
階段二:領域模塊升級優(yōu)化
1、在每個領域內(nèi),針對較難維護的領域模塊,進行二次重構(gòu)、升級、優(yōu)化;
核心思路:各個領域團隊內(nèi)部不定期發(fā)起重構(gòu)(可能大可能小),升級優(yōu)化自身負責的系統(tǒng)代碼,提升其可維護性及可擴展性。
本篇文章中,我們主要探討階段一,后續(xù)的重構(gòu)文章系列中,我們再逐步探討階段二的技術(shù)故事。
領域拆分與沉淀
1、領域劃分
在啟動重構(gòu)之前,至關(guān)重要的是要進行廣泛而深入的溝通與交流,以明確各個領域的定義、內(nèi)容及其邊界。這一過程中,我們應用DDD方法論,圍繞用戶故事對上層服務的業(yè)務邏輯,尤其是功能服務進行全面梳理。此環(huán)節(jié)需大量業(yè)務專家及系統(tǒng)負責人的積極參與,經(jīng)過多輪次的討論與碰撞,最終達成共識,并明確各領域的負責人。
接下來,我們將斗拱進件的對外API接口清單、控臺頁面菜單、內(nèi)部功能服務接口清單逐一列出,并根據(jù)其所屬領域進行歸類。對于那些同時涉及多個領域的頁面、內(nèi)外接口,就由熟悉相關(guān)業(yè)務的領導根據(jù)其經(jīng)驗作出決策,給到最合適的領域。
一旦確定了領域歸屬,接下來的任務便是從代碼層面著手,對所有的API服務、頁面及BFF(Backend For Frontend)服務、功能服務進行改造與遷移工作,將其合理地拆分并沉淀到各自對應的領域服務中。
2、代碼實施
整個代碼實施過程,分為五步:著色->代碼拆分->服務拆分>數(shù)據(jù)庫拆分->整理&優(yōu)化。
步驟一:著色
對后端BFF服務、功能層服務中,所有Public的API方法、Service方法、Repository方法等,添加自定義@Domain注解,以標識其所屬領域。對于被多個領域同時使用的公共方法,要么標記多個領域名稱,要么標記為All,即所有領域。
說明:此過程同樣涉及大量討論與交流。為了最終達成共識,建議按固定一個人的拆分思路統(tǒng)一開展,出現(xiàn)爭議由其來拍板,以免陷入無休止的爭辯而無法說服彼此的局面。
步驟二:代碼拆分
在不改變服務化結(jié)構(gòu)以及微服務調(diào)用入口的前提下,將@Domain標記的代碼及其遞歸引用的私有代碼,全部復制到為各個領域新建的獨立工程目錄中,此時需修改方法提供方及方法調(diào)用方相應的import包路徑,標記多個領域的方法就復制多份,標記為All的公共方法,就每個域都復制一份。
說明:這個過程比較復雜的地方是依賴識別,我們是在Idea插件的基礎上自主研發(fā)工具實現(xiàn)的。遷移完成后,新版本在生產(chǎn)環(huán)境中運行至少兩周,以驗證其穩(wěn)定性和可靠性。
步驟三:服務拆分
在調(diào)用入口不變的前提下,將跨領域的內(nèi)存調(diào)用代碼,轉(zhuǎn)成微服務遠程調(diào)用代碼。此時,微服務的數(shù)量會顯著增加,大致等于被拆分微服務個數(shù)乘以領域數(shù)量,此時每個服務就可以獨立迭代需求了。同樣,該過程也是通過工具完成,微服務調(diào)用代碼是按統(tǒng)一模版格式生成的,所以類及方法的命名會存在一些不人性化的情況。
說明:這個過程會遇到比較多的問題,如:內(nèi)存全局變量問題、數(shù)據(jù)庫事務問題、抽象繼承類問題、異步通知問題、超時時長設置問題等,我們在后續(xù)的重構(gòu)系列文章中再行展開。遷移完成后,新版本在生產(chǎn)環(huán)境中至少穩(wěn)定運行兩周,代表該階段的完成。
步驟四:數(shù)據(jù)庫拆分
為各領域創(chuàng)建獨立的數(shù)據(jù)庫,并將各領域獨有的庫表及數(shù)據(jù)內(nèi)容遷移至新庫中。針對那些跨多個領域的共享表,則要進行數(shù)據(jù)拆分與遷移,該一步驟相比前面的代碼拆分、服務拆分,工作量和復雜度都有顯著上升。鑒于該步驟短期內(nèi)難以完成,同時考慮到以下實際情況,我們決定暫時擱置,未來擇機實施:
● 對所有數(shù)據(jù)庫表,進行領域歸屬劃分,每張表僅歸屬一個域,針對該表的結(jié)構(gòu)及關(guān)鍵數(shù)據(jù)變更,均由所屬領域團隊完成,避免引入跨領域協(xié)作問題;
● 在服務拆分階段時,大部分跨領域的數(shù)據(jù)操作都已轉(zhuǎn)成微服務調(diào)用,只有少數(shù)因性能、事務等考量的查詢,直接通過Mapper跨庫訪問。
● 跨域共享表的數(shù)量不多,由其他域代為維護的規(guī)模不大,復雜度尚在可控范圍內(nèi);
步驟五:整理&優(yōu)化
拆到各個領域的微服務,整體是包含了原后端BFF服務、功能服務全部的源代碼,工程包非常大,類也非常多。各個團隊需要手動整理這些代碼,剔除不屬于本領域的部分以及未被使用到的函數(shù)和方法。此外,對于那些在服務拆分階段產(chǎn)生的工具型微服務接口,我們使用@Deprecated注解予以標記,明確表示這些接口不再接受迭代更新,若未來有需求要迭代,則按照標準的接口規(guī)范重新設計實施,以此逐步淘汰工具型的接口。
3、重構(gòu)效果
在歷時三個多月的項目實施過程中,我們遇到各種各樣的挑戰(zhàn),也逐一克服。整個項目的復雜度極高,參與進來的都是各團隊的核心成員及眾多資深架構(gòu)師。最終,項目也取得了令人矚目的成果。
從業(yè)務角度統(tǒng)計,進件相關(guān)需求的交付吞吐率提升了30%,缺陷密度(上線100個需求引入的缺陷數(shù))下降30%。
從技術(shù)層面來看,日常需求的協(xié)作模式從原先前端、功能和領域的多方協(xié)作,演變?yōu)閱蝹€領域團隊為主;所有原領域?qū)友邪l(fā)人員全面參與到業(yè)務開發(fā)中,顯著提升了他們的業(yè)務經(jīng)驗;所有技術(shù)TL積極參與了領域邊界的討論,對各自邊界有統(tǒng)一清晰的認識,很少再出現(xiàn)邊界爭論;原功能層的管理服務,在經(jīng)過重點拆分和沉淀后,系統(tǒng)的維護復雜度明顯降低。
總結(jié)
斗拱進件體系的重構(gòu)實踐,充分彰顯了直面技術(shù)債務的重要性。通過縝密規(guī)劃與嚴謹執(zhí)行,我們不僅實現(xiàn)了重構(gòu)的目標,還顯著提升了業(yè)務和技術(shù)效能。這不僅是對技術(shù)架構(gòu)的一次優(yōu)化,更是團隊協(xié)作能力與技術(shù)實力的全面提升。
免責聲明:以上內(nèi)容為本網(wǎng)站轉(zhuǎn)自其它媒體,相關(guān)信息僅為傳遞更多信息之目的,不代表本網(wǎng)觀點,亦不代表本網(wǎng)站贊同其觀點或證實其內(nèi)容的真實性。如稿件版權(quán)單位或個人不想在本網(wǎng)發(fā)布,可與本網(wǎng)聯(lián)系,本網(wǎng)視情況可立即將其撤除。
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。