該項(xiàng)目始于2009年,到現(xiàn)在已有7年的時(shí)間。在這7年中覆蓋的業(yè)務(wù)線不斷擴(kuò)大,從工單、差旅、計(jì)費(fèi)、文件、報(bào)表、增值業(yè)務(wù)等;業(yè)務(wù)流程從部分節(jié)點(diǎn)到用戶端的全線延伸;7年間打造多個(gè)產(chǎn)品,架構(gòu)經(jīng)歷了多次調(diào)整,從單體架構(gòu)、RPC、服務(wù)化、規(guī)模化到微服務(wù)。
主要架構(gòu)變遷如下圖所示:

在這7年架構(gòu)演進(jìn)路上,我們遇到的主要挑戰(zhàn)如下:
如何拆?即如何正確理解業(yè)務(wù),將單體結(jié)構(gòu)拆分為服務(wù)化架構(gòu)? 拆完后業(yè)務(wù)變了增加了怎么辦?即在業(yè)務(wù)需求不斷發(fā)展變化的前提下,如何持續(xù)快速地演進(jìn)? 如何安全地持續(xù)地拆?即如何在不影響當(dāng)下系統(tǒng)運(yùn)行狀態(tài)的前提下,持續(xù)安全地演進(jìn)? 如何保證拆對(duì)了? 拆完了怎么保證不被破壞? 問題1:如何將單體結(jié)構(gòu)拆分為服務(wù)化架構(gòu)?
就如庖丁解牛一樣,拆分需要摸清內(nèi)部的構(gòu)造脈絡(luò),在筋骨縫隙處下刀。那么微服務(wù)架構(gòu)中,我們認(rèn)為服務(wù)是業(yè)務(wù)能力的代表,需要圍繞業(yè)務(wù)進(jìn)行組織。拆分的關(guān)鍵在于正確理解業(yè)務(wù),識(shí)別單體內(nèi)部的業(yè)務(wù)領(lǐng)域及其邊界,并按邊界進(jìn)行拆分。
1. 識(shí)別業(yè)務(wù)領(lǐng)域及邊界。
首先需要將客戶、體驗(yàn)設(shè)計(jì)師、業(yè)務(wù)分析師、技術(shù)人員集結(jié)在一起對(duì)業(yè)務(wù)需求進(jìn)行溝通,隨后對(duì)其進(jìn)行領(lǐng)域劃分,確定限界上下文(Boundary Context),也稱戰(zhàn)略建模。
以下我們經(jīng)常使用的方法和參考的紅藍(lán)寶書:
Inception-> User Journey | Scenarios,用于梳理業(yè)務(wù)流程,由粗粒度到細(xì)粒度逐一場(chǎng)景分析。 四色建模,用于提取核心概念、關(guān)鍵數(shù)據(jù)項(xiàng)和業(yè)務(wù)約束。 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)-戰(zhàn)略設(shè)計(jì),用于劃分領(lǐng)域及邊界、進(jìn)行技術(shù)驗(yàn)證。 Eventstorming,用于提取領(lǐng)域中的業(yè)務(wù)事件,便于正確建模。

Inception與DDD戰(zhàn)略設(shè)計(jì)的對(duì)比:

一個(gè)業(yè)務(wù)領(lǐng)域或子域是一個(gè)企業(yè)中的業(yè)務(wù)范圍以及在其中進(jìn)行的活動(dòng),核心子域指業(yè)務(wù)成功的主要促成因素,是企業(yè)的核心競(jìng)爭(zhēng)力;通用子域不是核心,但被整個(gè)業(yè)務(wù)系統(tǒng)所使用;支撐子域不是核心,不被整個(gè)系統(tǒng)使用,該能力可從外部購(gòu)買。一個(gè)業(yè)務(wù)領(lǐng)域和子域可以包括多個(gè)業(yè)務(wù)能力,一個(gè)業(yè)務(wù)能力對(duì)應(yīng)一個(gè)服務(wù)。領(lǐng)域的邊界即限界上下文,也是服務(wù)的邊界,它封裝了一系列的領(lǐng)域模型。
一個(gè)業(yè)務(wù)流程代表了企業(yè)的一個(gè)業(yè)務(wù)領(lǐng)域,業(yè)務(wù)流程所涉及的數(shù)據(jù)或角色或是通用子域,或是支撐子域,由其在企業(yè)的核心競(jìng)爭(zhēng)力的角色所決定。比如企業(yè)有統(tǒng)一身份認(rèn)證,決策不同部門負(fù)責(zé)不同的流程任務(wù),那么身份認(rèn)證子域并不產(chǎn)生業(yè)務(wù)價(jià)值,不是業(yè)務(wù)成功的促成因素,但是所有流程的入口,因而為通用子域,可為單獨(dú)服務(wù);而部門負(fù)責(zé)的業(yè)務(wù)則為核心子域。
舉個(gè)例子
工單業(yè)務(wù)流程:
某企業(yè)為服務(wù)人員提供工單服務(wù)的業(yè)務(wù)流程簡(jiǎn)化如下。首先搜索服務(wù)人員,選取服務(wù)人員購(gòu)買的服務(wù),基于目標(biāo)國(guó)家的工單流程,向服務(wù)人員收取資料,對(duì)其進(jìn)行審計(jì),最后發(fā)送結(jié)果。
識(shí)別的領(lǐng)域:
其中服務(wù)為其核心競(jìng)爭(zhēng)能力,包括該企業(yè)對(duì)全球各國(guó)的政策理解,即法律流程,服務(wù)資料(問卷),計(jì)算服務(wù),資料審計(jì)服務(wù),相比其他競(jìng)爭(zhēng)對(duì)手的服務(wù)(價(jià)位/效率等),這些都為改企業(yè)提供核心的業(yè)務(wù)價(jià)值,自然也是核心子域。而其他用于統(tǒng)計(jì)改企業(yè)員工工作的工單,組織結(jié)構(gòu)和員工為支撐子域,并不直接產(chǎn)生業(yè)務(wù)價(jià)值

領(lǐng)域劃分的原則
在劃分的過程中,經(jīng)常糾結(jié)的一個(gè)問題是:這個(gè)模型(概念或數(shù)據(jù))看起來(lái)放這個(gè)領(lǐng)域合適,放另一個(gè)也合適,如何抉擇呢?
第一,依據(jù)該模型與邊界內(nèi)其他模型或角色關(guān)系的緊密程度。比如,是否當(dāng)該模型變化時(shí),其他模型也需要進(jìn)行變化;該數(shù)據(jù)是否通常由當(dāng)前上下文中的角色在當(dāng)前活動(dòng)范圍內(nèi)使用。 第二,服務(wù)邊界內(nèi)的業(yè)務(wù)能力職責(zé)應(yīng)單一,不是完成同一業(yè)務(wù)能力的模型不放在同一個(gè)上下文中。 第三,劃分的子域和服務(wù)需滿足正交原則。領(lǐng)域名字代表的自然語(yǔ)言上下文保持互相獨(dú)立。 第四,讀寫分離的原則。例如報(bào)表需有單獨(dú)報(bào)表子域。核心子域的劃分更多基于來(lái)自業(yè)務(wù)價(jià)值的產(chǎn)生方,而非不產(chǎn)生價(jià)值的報(bào)表系統(tǒng)。 第五,模型在很多業(yè)務(wù)操作中同時(shí)被修改和更新。 第六,組織中業(yè)務(wù)部分的劃分也是一種參考,一個(gè)業(yè)務(wù)部門的存在往往有其獨(dú)特的業(yè)務(wù)價(jià)值。
簡(jiǎn)單打個(gè)比方,同一個(gè)領(lǐng)域上下文中的模型要保持近親關(guān)系,五福以內(nèi),同一血統(tǒng)(業(yè)務(wù))。
領(lǐng)域劃分的誤區(qū)和建議 業(yè)務(wù)能力還是計(jì)算能力?在劃分一些貌似通用的領(lǐng)域時(shí),其實(shí)只是用到了通用的計(jì)算能力而不是業(yè)務(wù)能力,只需采用通用庫(kù)的方式進(jìn)行封裝,而無(wú)需使用服務(wù)的方式。如我們系統(tǒng)的模板服務(wù),是構(gòu)建通用的模板服務(wù),服務(wù)于整個(gè)平臺(tái)的服務(wù);還是每個(gè)服務(wù)擁有獨(dú)立的模板模塊? 盡早識(shí)別剝離通用領(lǐng)域。如身份認(rèn)證與鑒權(quán)領(lǐng)域,是企業(yè)系統(tǒng)中最復(fù)雜、有相對(duì)多變的領(lǐng)域,需要及早隔離它對(duì)核心業(yè)務(wù)的干擾。 時(shí)刻促成技術(shù)人員與客戶、業(yè)務(wù)人員的對(duì)話。業(yè)務(wù)領(lǐng)域的劃分離不開對(duì)業(yè)務(wù)意圖的真正理解。而需求人員和體驗(yàn)設(shè)計(jì)師對(duì)于User Journey的使用更熟悉,而技術(shù)人員、架構(gòu)師對(duì)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)、Eventstorming更熟悉。不管哪種方法都要求跨角色的群體協(xié)同工作,即客戶人員、業(yè)務(wù)分析師、體驗(yàn)設(shè)計(jì)師與技術(shù)人員、架構(gòu)師。而現(xiàn)實(shí)的情況中,User Journey更多的在Inception,在需求階段進(jìn)行,而領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)、Eventstorming更多的在開發(fā)設(shè)計(jì)階段被使用,故而需求階段經(jīng)常缺失技術(shù)人員,而開發(fā)設(shè)計(jì)階段經(jīng)常缺失客戶、業(yè)務(wù)人員的參與。 另一個(gè)常見的現(xiàn)象是,Inception的參與人員和真正的開發(fā)團(tuán)隊(duì)有可能不是同一個(gè)群體,那么Inception中的業(yè)務(wù)溝通往往以UI的方式作為傳遞,因此在開發(fā)中經(jīng)常只能通過UI設(shè)計(jì)來(lái)理解業(yè)務(wù)的真正意圖。 所以要想將正確的理解業(yè)務(wù),做對(duì)軟件,需要時(shí)刻促成技術(shù)人員與客戶、業(yè)務(wù)人員的對(duì)話。
識(shí)別了被拆對(duì)象的結(jié)構(gòu)和邊界,下一步需要決定拆分的策略和拆分的步驟。
2.拆分方法與策略
拆分方法需要根據(jù)遺留系統(tǒng)的狀態(tài),通常分為絞殺者與修繕者兩種模式。
絞殺者模式 指在遺留系統(tǒng)外圍,將新功能用新的方式構(gòu)建為新的服務(wù)。隨著時(shí)間的推移,新的服務(wù)逐漸“絞殺”老的一流系統(tǒng)。對(duì)于那些老舊龐大難以更改的遺留系統(tǒng),推薦采用絞殺者模式。 修繕者模式 就如修房或修路一樣,將老舊待修繕的部分進(jìn)行隔離,用新的方式對(duì)其進(jìn)行單獨(dú)修復(fù)。修復(fù)的同時(shí),需保證與其他部分仍能協(xié)同功能。
我們過去所做的拆分中多為修繕者模式,其基本原理來(lái)自Martin Fowler的branch by abstraction的重構(gòu)方法,如下圖所示:

就如我們團(tuán)隊(duì)所總結(jié)的16字重構(gòu)箴言,我覺得十分的貼切:
“舊的不變,新的創(chuàng)建,一步切換,舊的再見”。
通過識(shí)別內(nèi)部的被拆模塊,對(duì)其增加接口層,將舊的引用改為新接口調(diào)用;隨后將接口封裝為API,并將對(duì)接口的引用改為本地API調(diào)用;最后將新服務(wù)部署為新進(jìn)程,調(diào)用改為真正的服務(wù)API調(diào)用。
同時(shí),拆分建議從業(yè)務(wù)相對(duì)獨(dú)立、耦合度最小的地方開始。待團(tuán)隊(duì)獲取相應(yīng)經(jīng)驗(yàn)和基礎(chǔ)設(shè)施平臺(tái)構(gòu)建完善后,再進(jìn)行核心應(yīng)用遷移和大規(guī)模的改造。另外,核心通用服務(wù)盡量先行,如身份認(rèn)證服務(wù)。
3. 拆分步驟
對(duì)于模塊的拆分包括兩部分:數(shù)據(jù)庫(kù)與業(yè)務(wù)代碼,可以先數(shù)據(jù)庫(kù)后業(yè)務(wù)代碼,亦可先業(yè)務(wù)代碼后數(shù)據(jù)庫(kù)。然而我們的項(xiàng)目拆分中遇到的最大挑戰(zhàn)是數(shù)據(jù)層的拆分。在2015年的拆分中發(fā)現(xiàn),數(shù)據(jù)庫(kù)層由于當(dāng)時(shí)系統(tǒng)性能調(diào)優(yōu)的驅(qū)動(dòng),在代碼中出現(xiàn)了跨模塊的數(shù)據(jù)庫(kù)連表查詢。這導(dǎo)致后期服務(wù)的拆分非常的困難。因此在拆分步驟上我們更多的推薦數(shù)據(jù)庫(kù)先行。
4.數(shù)據(jù)庫(kù)拆分
我們借鑒了重構(gòu)數(shù)據(jù)庫(kù)一書中提到的方法,通過重復(fù)schema同步數(shù)據(jù),對(duì)數(shù)據(jù)庫(kù)的讀寫操作分別進(jìn)行遷移。如下圖所示:
雖然技術(shù)上是可行的,然而這仍然占用了大量不必要的時(shí)間,包括大量的數(shù)據(jù)遷移。這也是導(dǎo)致當(dāng)時(shí)的拆分無(wú)法在給定時(shí)間內(nèi)完成的很大因素。
5. 我們的結(jié)果:
系統(tǒng)架構(gòu)圖:

問題2:拆分后業(yè)務(wù)變了增加了怎么辦?
隨著客戶業(yè)務(wù)的變化,我們的服務(wù)也在持續(xù)的增加,而其中碰到了一個(gè)特大的服務(wù)。服務(wù)的大小如何衡量呢?該服務(wù)生產(chǎn)代碼7萬(wàn)行 ,測(cè)試代碼14萬(wàn)行 ,測(cè)試運(yùn)行時(shí)間2個(gè)小時(shí)。團(tuán)隊(duì)中7個(gè)stream每天50%%u5DE5作需要對(duì)這個(gè)服務(wù)進(jìn)行更改,使得團(tuán)隊(duì)間的依賴非常嚴(yán)重,獨(dú)立功能無(wú)法單獨(dú)快速前行,交付速度及質(zhì)量都受到了影響。
我們的總結(jié):
客戶的業(yè)務(wù)是在變化的,我們對(duì)業(yè)務(wù)的認(rèn)知也是逐漸的過程,所以Martin Fowler在他的文章中提出,系統(tǒng)的初期建議以單體結(jié)構(gòu)開始,隨業(yè)務(wù)發(fā)展決定其是否被拆分或合并。那么這也意味著這樣構(gòu)建的服務(wù)在它的生命周期中必然會(huì)持續(xù)被拆分或合并。那么為了實(shí)現(xiàn)這樣一個(gè)目標(biāo),使系統(tǒng)擁有快速的響應(yīng)力,也要求這樣的拆分必然是高效的低成本的。
因此,服務(wù)的設(shè)計(jì)需要滿足如下的原則:
服務(wù)要有明確的業(yè)務(wù)邊界,以單體開始并不意味著沒有邊界。 服務(wù)要有邊界,即使以單體開始也要定義單體時(shí)期的邊界。我們系統(tǒng)中有一個(gè)名為“Monkey”的服務(wù),是在中國(guó)虎年啟動(dòng)的,由此它并不是一個(gè)業(yè)務(wù)概念。當(dāng)這個(gè)服務(wù)的名字為MonkeyAPI時(shí),可以想象5年來(lái)它變成了什么?幾乎所有和這個(gè)產(chǎn)品相關(guān)的功能都放入了這個(gè)服務(wù)中。脫離平臺(tái)來(lái)看這一個(gè)產(chǎn)品的系統(tǒng),其實(shí)它只是做了前后端分離而已。這個(gè)例子告訴我們,沒有邊界就會(huì)導(dǎo)致大雜燴,之后對(duì)其進(jìn)行整理和重造的代價(jià)很大,可能需要花費(fèi)“幾代人”的努力。 服務(wù)要有明確清晰的契約設(shè)計(jì),即對(duì)外提供的業(yè)務(wù)能力。 服務(wù)內(nèi)部要保持高度模塊化,才能夠容易的被拆分。 可測(cè)試。 問題3:如何安全地持續(xù)地拆?
就如前言中提到的,系統(tǒng)已經(jīng)上線大量的用戶正在使用,如何在不影響當(dāng)下系統(tǒng)運(yùn)行狀態(tài)的前提下,持續(xù)安全地演進(jìn)?其實(shí)持續(xù)演進(jìn)就是一場(chǎng)架構(gòu)層次的重構(gòu),在這樣的路上同樣需要:
壞味道驅(qū)動(dòng),架構(gòu)的壞味道是代碼壞味道在更高層次的展現(xiàn),也就意味著架構(gòu)的混亂程度同樣反映了該系統(tǒng)代碼層的質(zhì)量問題。 安全小步的重構(gòu)。 有足夠的測(cè)試進(jìn)行保護(hù)——契約測(cè)試。 持續(xù)驗(yàn)證演進(jìn)的方向。 真正有挑戰(zhàn)的問題4:如何保證拆對(duì)了?
拆分不能沒有目標(biāo),尤其在具有風(fēng)險(xiǎn)的架構(gòu)層次拆分更需謹(jǐn)慎。那么我們?nèi)绾悟?yàn)證拆分的結(jié)果和收益?或許它可以提高開發(fā)效率,交付速度快,上線快,宕機(jī)時(shí)間也短,還能提高開發(fā)質(zhì)量,可擴(kuò)展性好,穩(wěn)定,維護(hù)成本低,新人成長(zhǎng)快,團(tuán)隊(duì)容易掌握等等。然而軟件開發(fā)是一個(gè)復(fù)雜的事情,拆分可以引起多個(gè)維度的變化,度量的難度在于如何準(zhǔn)確定位由拆分這一單一因素引起的價(jià)值的變化(增加或降低)。
其實(shí)要回答這個(gè)問題,還是要回到拆分之初:為什么而拆? 我所見過的案例中有因?yàn)檎卧虿鸬摹I(yè)務(wù)發(fā)展需要的、系統(tǒng)集成驅(qū)動(dòng)的等等;有因之而成功的,也有因之而失敗的。拆并不是一件容易的事,有諸多的因素。我認(rèn)為不管表象是什么,拆之前需要弄清拆分的價(jià)值所在,這也是我們可以保證拆分結(jié)果的源頭。
總結(jié)
系統(tǒng)可由單體結(jié)構(gòu)開始,不斷的演進(jìn)。而團(tuán)隊(duì)需要對(duì)業(yè)務(wù)保持敏感,與客戶、業(yè)務(wù)人員進(jìn)行業(yè)務(wù)對(duì)話,不斷修煉領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)和重構(gòu)的能力。
在拆分的路上,我們的經(jīng)驗(yàn)顯示其最大的障礙來(lái)自意大利面一樣的系統(tǒng)。不管我們是什么樣的架構(gòu)風(fēng)格,高內(nèi)聚低耦合的模塊化代碼內(nèi)部質(zhì)量仍然是我們架構(gòu)演進(jìn)的基石。具有夯實(shí)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)和重構(gòu)功底的團(tuán)隊(duì)才可以應(yīng)對(duì)這些挑戰(zhàn),持續(xù)演進(jìn),保持其生命力。而架構(gòu)變遷之前需要弄清背后的變遷動(dòng)因與價(jià)值,探索性前進(jìn),及時(shí)反饋驗(yàn)證,才是正解。那么我們?nèi)绾伪WC架構(gòu)不被破壞呢?這個(gè)問題會(huì)在后續(xù)的文章中持續(xù)探討。
最后,勿忘初心,且行且演進(jìn)。
更多關(guān)于云服務(wù)器,域名注冊(cè),虛擬主機(jī)的問題,請(qǐng)?jiān)L問三五互聯(lián)官網(wǎng):m.shinetop.cn