長文多圖:結(jié)合DDD講清楚編寫技術(shù)方案的七大維度
點擊上方“服務(wù)端思維”,選擇“設(shè)為星標”
回復”669“獲取獨家整理的精選資料集
回復”加群“加入全國服務(wù)端高端社群「后端圈」
1 為什么要寫技術(shù)方案
回顧軟件開發(fā)的歷史進程,我們可以將其分為程序設(shè)計時代、程序系統(tǒng)時代和軟件工程時代三大歷史階段。
在程序設(shè)計時代(1946-1956),軟件開發(fā)主要依賴于個人編程技巧,技術(shù)文檔只要存在個人開發(fā)者的大腦即可,因為沒有溝通和協(xié)作需要,編寫技術(shù)文檔也不具有緊迫性。
在程序系統(tǒng)時代(1956-1968),計算機性能顯著提升,應(yīng)用范圍和規(guī)模逐步擴大,以至于依靠個人無法完成軟件的開發(fā),所以出現(xiàn)了團隊合作。在早期團隊合作過程中,開發(fā)者仍然保持了早期各自為戰(zhàn)的開發(fā)習慣,即使出現(xiàn)了一些方法論雛形,也無法從根本上控制溝通和協(xié)作的巨大成本,軟件危機就此出現(xiàn)。1968年國際學術(shù)會議提出了軟件危機和軟件工程的概念。
軟件危機的定義是落后的軟件生產(chǎn)方式無法滿足迅速增長的計算機軟件需求,從而導致開發(fā)與維護過程中出現(xiàn)一系列嚴重問題的現(xiàn)象。軟件的工程定義是建立并使用完善的工程化原則,以較經(jīng)濟的手段獲得能在實際機器上有效運行的可靠軟件的一系列方法
從此軟件開發(fā)進入工程化階段,也應(yīng)運而生了大量開發(fā)方法論和開發(fā)模型。其中標準和完善的文檔是軟件工程重要組成部分,可以很大程度上減少溝通和協(xié)作成本,而技術(shù)方案又是技術(shù)文檔重要組成部分。
2 技術(shù)方案要體現(xiàn)什么
軟件系統(tǒng)生命周期包括定義、開發(fā)、運維、消亡這四大階段。定義階段包括定義問題、可行性研究和需求分析。開發(fā)階段包括概要設(shè)計、詳細設(shè)計、編碼和測試。運維階段包括更正性維護、適應(yīng)性維護、預防性維護和完善性維護。消亡階段包括系統(tǒng)報廢和優(yōu)雅下線。

生命周期每個階段固然有各自的重要性,但是開發(fā)者更應(yīng)該關(guān)注定義階段與開發(fā)階段。定義階段需要解決為什么開發(fā)(why)、需求是什么(what)兩個問題,開發(fā)階段需要解決怎么設(shè)計,怎么編碼,怎么測試(how)三個問題。
技術(shù)方案是否需要體現(xiàn)定義和開發(fā)的所有子階段?我認為也無必要。問題定義和可行性研究主要由產(chǎn)品經(jīng)理負責,測試階段主要由測試人員負責,開發(fā)者可以關(guān)注但不是必須體現(xiàn)在技術(shù)方案。我認為技術(shù)方案必須要體現(xiàn)需求分析、概要設(shè)計、詳細設(shè)計、編碼四個子階段。
3 七大維度
我認為一份完整技術(shù)方案應(yīng)該至少具有七大維度,每個維度描述系統(tǒng)的一個側(cè)面,組合在一起最終描繪出整個系統(tǒng),這些維度分別是:
四色分領(lǐng)域
用例看功能
流程三劍客
領(lǐng)域與數(shù)據(jù)
縱橫做設(shè)計
分層看架構(gòu)
接口看對接
本文我們分析一個足球運動員信息管理系統(tǒng),這個系統(tǒng)我們可能也都沒有做過,正好一起分析這個系統(tǒng)。需要說明本文著重介紹方法論的落地,業(yè)務(wù)細節(jié)難以面面俱到。
3.1 四色分領(lǐng)域
3.1.1 流程梳理
首先梳理業(yè)務(wù)流程,這里有兩個問題需要考慮,第一個問題是從什么視角去梳理?因為不同的人看到的流程是不一樣的。答案是取決于系統(tǒng)需要解決什么問題,因為我們要管理運動員從轉(zhuǎn)會到上場比賽整條鏈路信息,所以從運動員視角出發(fā)是一個合適的選擇。
第二個問題是對業(yè)務(wù)不熟悉怎么辦?因為我們不是體育和運動專家,并不清楚整條鏈路的業(yè)務(wù)細節(jié)。答案是梳理流程時一定要有業(yè)務(wù)專家在場,因為沒有真實業(yè)務(wù)細節(jié),無法領(lǐng)域驅(qū)動設(shè)計。同理在互聯(lián)網(wǎng)梳理復雜業(yè)務(wù)流程時,一定要有對相關(guān)業(yè)務(wù)熟悉的產(chǎn)品經(jīng)理或者運營一起參與。
假設(shè)足球業(yè)務(wù)專家梳理出了業(yè)務(wù)流程,運動員提出轉(zhuǎn)會,協(xié)商一致后到新俱樂部體檢,體檢通過就進行簽約。進入新俱樂部后進行訓練,訓練指標達標后上場比賽,賽后參加新聞發(fā)布會。實際流程會復雜很多,本文還是著重講解方法論。

3.1.2 四色建模
(1) 時標對象
四色建模第一種顏色是紅色,表示時標對象。時標對象是四色建模最重要的對象,可以理解為核心業(yè)務(wù)單據(jù)。在業(yè)務(wù)過程中一定要對關(guān)鍵業(yè)務(wù)留下單據(jù),通過這些單據(jù)可以追溯整個業(yè)務(wù)流程。
時標對象具有兩個特點:第一是事實不可變性,記錄了過去某個時間點或時間段內(nèi)發(fā)生的事實。第二是責任可追溯性,記錄了管理者關(guān)注的信息?,F(xiàn)在我們分析本系統(tǒng)時標對象有哪些,需要留下哪些核心業(yè)務(wù)單據(jù)。
轉(zhuǎn)會對應(yīng)轉(zhuǎn)會單據(jù),體檢對應(yīng)體檢單據(jù),簽合同對應(yīng)合同單據(jù),訓練對應(yīng)訓練指標單據(jù),比賽對應(yīng)比賽指標單據(jù),新聞發(fā)布會對應(yīng)采訪單據(jù)。根據(jù)分析繪制如下時標對象:

(2) 參與方、地、物
這三類對象在四色建模中用綠色表示,我們以電商場景為例進行說明。用戶支付購買商家的商品時,用戶和商家是參與方。物流系統(tǒng)發(fā)貨時配送單據(jù)需要有配送地址對象,地址對象就是地。訂單需要商品對象,物流配送需要有貨品,商品和貨品就是物。
我們分析本例可以知道參與方包含總經(jīng)理、隊醫(yī)、教練、球迷、記者,地包含訓練地址、比賽地址、采訪地址,物包含簽名球衣和簽名足球:

(3) 角色對象
在四色建模中用黃色表示,這類對象表示參與方、地、物以什么角色參與到業(yè)務(wù)流程:

(4) 描述對象
我們可以為對象增加相關(guān)描述信息,在四色建模中用藍色表示:

3.1.3 劃分領(lǐng)域
在四色建模過程中我們體會到時標對象是最重要的對象,因為其承載了業(yè)務(wù)系統(tǒng)核心單據(jù)。在劃分領(lǐng)域時我們同樣離不開時標對象,通過收斂相關(guān)時標對象劃分領(lǐng)域。

3.1.4 領(lǐng)域事件
當業(yè)務(wù)系統(tǒng)發(fā)生一件事情時,如果本領(lǐng)域或其它領(lǐng)域有后續(xù)動作跟進,那么我們把這件事情稱為領(lǐng)域事件,這個事件需要被感知。
例如球員比賽受傷,這是比賽子域事件,但是醫(yī)療和訓練子域是需要感知的,那么比賽子域就發(fā)出一個事件,醫(yī)療和訓練子域會訂閱。球員比賽取得進球,這也是比賽子域事件,但是訓練和合同子域也會關(guān)注這個事件,所以比賽子域也會發(fā)出一個比賽進球事件,訓練和合同子域會訂閱。
通過事件交互有一個問題需要注意,通過事件訂閱實現(xiàn)業(yè)務(wù)只能采用最終一致性,需要放棄強一致性,可能會引入新的復雜度需要權(quán)衡。

3.2 用例看功能
目前為止領(lǐng)域已經(jīng)確定了,大領(lǐng)域已經(jīng)拆分成了小領(lǐng)域,我們已經(jīng)不再束手無策,而是可以對小領(lǐng)域進行用例分析了。用例圖由參與者和用例組成,目的是回答這樣一個問題:什么人使用系統(tǒng)干什么事。
下圖表示在比賽領(lǐng)域,運動員視角(什么人)使用系統(tǒng)進行進球統(tǒng)計,助攻統(tǒng)計,犯規(guī)統(tǒng)計,跑動距離統(tǒng)計,比賽評分統(tǒng)計,傳球成功率統(tǒng)計,受傷統(tǒng)計(干什么事),同理也可以選擇四色建模中其它參與者視角繪制用例圖。

include關(guān)鍵字表示包含關(guān)系。例如比賽是基用例,包含了進球統(tǒng)計,助攻統(tǒng)計,犯規(guī)統(tǒng)計,跑動距離統(tǒng)計,比賽評分統(tǒng)計,傳球成功率統(tǒng)計,受傷統(tǒng)計七個子用例。包含關(guān)系表示法有兩個優(yōu)點:第一是可以清晰地組織子用例,第二是有利于子用例復用,例如主教練視角用例圖也包含比賽評分,那么就可以直接指向比賽評分子用例。
extend關(guān)鍵字表示擴展關(guān)系。例如點球統(tǒng)計是進球統(tǒng)計的擴展,因為不一定可以獲得點球,所以點球統(tǒng)計即使不存在,也不會影響進球統(tǒng)計功能。黃牌統(tǒng)計、紅牌統(tǒng)計是犯規(guī)統(tǒng)計的擴展,因為普通犯規(guī)不會獲得紅黃牌,所以紅黃牌統(tǒng)計不存在,也不會影響犯規(guī)統(tǒng)計功能。
用例圖不關(guān)心實現(xiàn)細節(jié),而是從外部視角描述系統(tǒng)功能,即使不了解實現(xiàn)細節(jié)的人,通過看用例圖也可以快速了解系統(tǒng)功能,這個特性規(guī)定了用例圖不宜過于復雜,能夠說明核心功能即可。
3.3 流程三劍客
用例圖是從外部視角描述系統(tǒng),但是分析系統(tǒng)總是要深入系統(tǒng)內(nèi)部的,其中流程視圖就是描述系統(tǒng)內(nèi)如何流轉(zhuǎn)的視圖。
活動圖、序列圖、狀態(tài)機圖是流程視圖中最重要的三種視圖,我們稱為流程三劍客。三者側(cè)重點有所不同:活動圖側(cè)重于邏輯分支,順序圖側(cè)重于交互,狀態(tài)機圖側(cè)重于狀態(tài)流轉(zhuǎn)。
3.3.1 活動圖
活動圖適合描述復雜邏輯分支,設(shè)想這樣一種業(yè)務(wù)場景,球隊需要選拔一名球員成為足球先生,選拔標準如下:前場、中場、后場、門將各選出一名候選球員。前場隊員依次比較進球數(shù)、助攻數(shù),中場隊員依次比較助攻數(shù)、搶斷數(shù),后場隊員依次比較解圍數(shù)、搶斷數(shù),門將依次比較撲救數(shù)、撲點數(shù),如果所有指標均相同則抽簽。每個位置有人選之后,全體教練組投票,如果投票數(shù)相同則抽簽。
業(yè)界流傳著一句話:一圖勝千言,其中一個重要原因是文字是線性的,所以表達邏輯分支能力不如流程視圖,而在流程視圖中表達邏輯分支能力最強正是活動圖。

3.3.2 順序圖
順序圖側(cè)重于交互,適合按照時間順序體現(xiàn)一個業(yè)務(wù)流程中交互細節(jié),但是順序圖并不擅長體現(xiàn)復雜邏輯分支。
如果某個邏輯分支特別重要,可以選擇再畫一個順序圖。例如支付流程中有支付成功正常流程,也有支付失敗異常流程,這兩個流程都非常重要,所以可以用兩張順序圖體現(xiàn)?;氐奖疚膶嵗?,我們可以通過順序圖體現(xiàn)球員從提出轉(zhuǎn)會到比賽全流程。

3.3.3 狀態(tài)機圖
假設(shè)一條數(shù)據(jù)有ABC三種狀態(tài),從正常業(yè)務(wù)角度來看,狀態(tài)只能從A流轉(zhuǎn)到B,再從B流轉(zhuǎn)到C,不能亂序也不可逆。但是可能出現(xiàn)這種異常情況:數(shù)據(jù)當前狀態(tài)為A,接收異步消息更改狀態(tài),B消息由于延時晚于C消息,最終導致狀態(tài)先改為C再改為B,那么此時狀態(tài)就是錯誤的。
狀態(tài)機圖側(cè)重于狀態(tài)流轉(zhuǎn),說明了哪些狀態(tài)之間可以相互流轉(zhuǎn),在實際開發(fā)中再結(jié)合狀態(tài)機代碼模式,可以解決上述狀態(tài)異常情況?;氐奖疚膶嵗覀兛梢酝ㄟ^狀態(tài)機圖表示球員從提出轉(zhuǎn)會到簽約整個狀態(tài)流程。

3.4 領(lǐng)域與數(shù)據(jù)
上述章節(jié)從功能層面和流程層面進行了系統(tǒng)分析,現(xiàn)在從數(shù)據(jù)層分析系統(tǒng),我們首先對比兩組概念:值對象與實體,領(lǐng)域?qū)ο笈c數(shù)據(jù)對象。
實體是具有唯一標識的對象,唯一標識會伴隨實體對象整個生命周期并且不可變更。值對象本質(zhì)上是屬性的集合,沒有唯一標識。
領(lǐng)域?qū)ο笈c數(shù)據(jù)對象一個重要的區(qū)別是值對象存儲方式。領(lǐng)域?qū)ο笤诎祵ο蟮耐瑫r也保留了值對象的業(yè)務(wù)含義,而數(shù)據(jù)對象可以使用更加松散的結(jié)構(gòu)保存值對象,簡化數(shù)據(jù)庫設(shè)計。
現(xiàn)在我們需要管理足球運動員基本信息和比賽數(shù)據(jù),對應(yīng)領(lǐng)域模型和數(shù)據(jù)模型應(yīng)該如何設(shè)計?姓名、身高、體重是一名運動員本質(zhì)屬性,加上唯一編號可以對應(yīng)實體對象。跑動距離,傳球成功率,進球數(shù)是運動員比賽表現(xiàn),這些屬性的集合可以對應(yīng)值對象。

我們根據(jù)圖示編寫領(lǐng)域?qū)ο笈c數(shù)據(jù)對象代碼:
//?數(shù)據(jù)對象
public?class?FootballPlayerDO?{
????private?Long?id;
????private?String?name;
????private?Integer?height;
????private?Integer?weight;
????private?String?gamePerformance;
}
//?領(lǐng)域?qū)ο?/span>
public?class?FootballPlayerDMO?{
????private?Long?id;
????private?String?name;
????private?Integer?height;
????private?Integer?weight;
????private?GamePerformanceVO?gamePerformanceVO;
}
public?class?GamePerformanceVO?{
????private?Double?runDistance;
????private?Double?passSuccess;
????private?Integer?scoreNum;
}
為什么要采用JSON存儲值對象?因為腳本化是一種拓展靈活性的方式,腳本化不僅指使用groovy、QLExpress腳本增強系統(tǒng)靈活性,還包括松散可擴展的數(shù)據(jù)結(jié)構(gòu)。數(shù)據(jù)模型抽象出了姓名、身高、體重這些基本屬性,對于頻繁變化的比賽表現(xiàn)屬性,這些屬性值可能經(jīng)常變化,甚至屬性本身也是經(jīng)常變化,可能會加上射門次數(shù),突破次數(shù)等,所以采用松散結(jié)構(gòu)進行存儲。
如果需要根據(jù)JSON結(jié)構(gòu)中KEY進行檢索,例如查詢進球數(shù)大于5的球員,這也不是沒有辦法。我們可以將MySQL表中數(shù)據(jù)平鋪到ES中,一條數(shù)據(jù)根據(jù)JSON KEY平鋪變成為多條數(shù)據(jù),這樣就可以進行檢索了。
3.5 縱橫做設(shè)計
復雜業(yè)務(wù)之所以復雜,一個重要原因是涉及角色或者類型較多,很難平鋪直敘地進行設(shè)計,所以我們需要增加分析維度。其中最常見的是增加橫向和縱向兩個維度,本文也著重討論兩個維度??傮w而言橫向擴展的是思考廣度,縱向擴展的是思考深度,對應(yīng)到系統(tǒng)設(shè)計而言可以總結(jié)為:縱向做隔離,橫向做編排。
我們首先分析一個下單場景。當前有ABC三種訂單類型:A訂單價格9折,物流最大重量不能超過9公斤,不支持退款。B訂單價格8折,物流最大重量不能超過8公斤,支持退款。C訂單價格7折,物流最大重量不能超過7公斤,支持退款。按照需求字面含義平鋪直敘地寫代碼也并不難:
public?class?OrderServiceImpl?implements?OrderService?{
????@Resource
????private?OrderMapper?orderMapper;
????@Override
????public?void?createOrder(OrderBO?orderBO)?{
????????if?(null?==?orderBO)?{
????????????throw?new?RuntimeException("參數(shù)異常");
????????}
????????if?(OrderTypeEnum.isNotValid(orderBO.getType()))?{
????????????throw?new?RuntimeException("參數(shù)異常");
????????}
????????//?A類型訂單
????????if?(OrderTypeEnum.A_TYPE.getCode().equals(orderBO.getType()))?{
????????????orderBO.setPrice(orderBO.getPrice()?*?0.9);
????????????if?(orderBO.getWeight()?>?9)?{
????????????????throw?new?RuntimeException("超過物流最大重量");
????????????}
????????????orderBO.setRefundSupport(Boolean.FALSE);
????????}
????????//?B類型訂單
????????else?if?(OrderTypeEnum.B_TYPE.getCode().equals(orderBO.getType()))?{
????????????orderBO.setPrice(orderBO.getPrice()?*?0.8);
????????????if?(orderBO.getWeight()?>?8)?{
????????????????throw?new?RuntimeException("超過物流最大重量");
????????????}
????????????orderBO.setRefundSupport(Boolean.TRUE);
????????}
????????//?C類型訂單
????????else?if?(OrderTypeEnum.C_TYPE.getCode().equals(orderBO.getType()))?{
????????????orderBO.setPrice(orderBO.getPrice()?*?0.7);
????????????if?(orderBO.getWeight()?>?7)?{
????????????????throw?new?RuntimeException("超過物流最大重量");
????????????}
????????????orderBO.setRefundSupport(Boolean.TRUE);
????????}
????????//?保存數(shù)據(jù)
????????OrderDO?orderDO?=?new?OrderDO();
????????BeanUtils.copyProperties(orderBO,?orderDO);
????????orderMapper.insert(orderDO);
????}
}
上述代碼從功能上完全可以實現(xiàn)業(yè)務(wù)需求,但是程序員不僅要滿足功能,還需要思考代碼的可維護性。如果新增一種訂單類型,或者新增一個訂單屬性處理邏輯,那么我們就要在上述邏輯中新增代碼,如果處理不慎就會影響原有邏輯。
為了避免牽一發(fā)而動全身這種情況,設(shè)計模式中的開閉原則要求我們面向新增開放,面向修改關(guān)閉,我認為這是設(shè)計模式中最重要的一條原則。
需求變化通過擴展,而不是通過修改已有代碼實現(xiàn),這樣就保證代碼穩(wěn)定性。擴展也不是隨意擴展,因為事先定義了算法,擴展也是根據(jù)算法擴展,用抽象構(gòu)建框架,用實現(xiàn)擴展細節(jié)。標準意義的二十三種設(shè)計模式說到底最終都是在遵循開閉原則。
如何改變平鋪直敘的思考方式?這就要為問題分析加上縱向和橫向兩個維度,我選擇使用分析矩陣方法,其中縱向表示策略,橫向表示場景:

3.5.1 縱向做隔離
縱向維度表示策略,不同策略在邏輯上和業(yè)務(wù)上應(yīng)該是隔離的,本實例包括優(yōu)惠策略、物流策略和退款策略,策略作為抽象,不同訂單類型去擴展這個抽象,策略模式非常適合這種場景。本文詳細分析優(yōu)惠策略,物流策略和退款策略同理。
//?優(yōu)惠策略
public?interface?DiscountStrategy?{
????public?void?discount(OrderBO?orderBO);
}
//?A類型優(yōu)惠策略
@Component
public?class?TypeADiscountStrategy?implements?DiscountStrategy?{
????@Override
????public?void?discount(OrderBO?orderBO)?{
????????orderBO.setPrice(orderBO.getPrice()?*?0.9);
????}
}
//?B類型優(yōu)惠策略
@Component
public?class?TypeBDiscountStrategy?implements?DiscountStrategy?{
????@Override
????public?void?discount(OrderBO?orderBO)?{
????????orderBO.setPrice(orderBO.getPrice()?*?0.8);
????}
}
//?C類型優(yōu)惠策略
@Component
public?class?TypeCDiscountStrategy?implements?DiscountStrategy?{
????@Override
????public?void?discount(OrderBO?orderBO)?{
????????orderBO.setPrice(orderBO.getPrice()?*?0.7);
????}
}
//?優(yōu)惠策略工廠
@Component
public?class?DiscountStrategyFactory?implements?InitializingBean?{
????private?Map?strategyMap?=?new?HashMap<>();
????@Resource
????private?TypeADiscountStrategy?typeADiscountStrategy;
????@Resource
????private?TypeBDiscountStrategy?typeBDiscountStrategy;
????@Resource
????private?TypeCDiscountStrategy?typeCDiscountStrategy;
????public?DiscountStrategy?getStrategy(String?type)?{
????????return?strategyMap.get(type);
????}
????@Override
????public?void?afterPropertiesSet()?throws?Exception?{
????????strategyMap.put(OrderTypeEnum.A_TYPE.getCode(),?typeADiscountStrategy);
????????strategyMap.put(OrderTypeEnum.B_TYPE.getCode(),?typeBDiscountStrategy);
????????strategyMap.put(OrderTypeEnum.C_TYPE.getCode(),?typeCDiscountStrategy);
????}
}
//?優(yōu)惠策略執(zhí)行
@Component
public?class?DiscountStrategyExecutor?{
????private?DiscountStrategyFactory?discountStrategyFactory;
????public?void?discount(OrderBO?orderBO)?{
????????DiscountStrategy?discountStrategy?=?discountStrategyFactory.getStrategy(orderBO.getType());
????????if?(null?==?discountStrategy)?{
????????????throw?new?RuntimeException("無優(yōu)惠策略");
????????}
????????discountStrategy.discount(orderBO);
????}
}
3.5.2 橫向做編排
橫向維度表示場景,一種訂單類型在廣義上可以認為是一種業(yè)務(wù)場景,在場景中將獨立的策略進行串聯(lián),模板方法設(shè)計模式適用于這種場景。
模板方法模式一般使用抽象類定義算法骨架,同時定義一些抽象方法,這些抽象方法延遲到子類實現(xiàn),這樣子類不僅遵守了算法骨架約定,也實現(xiàn)了自己的算法。既保證了規(guī)約也兼顧靈活性,這就是用抽象構(gòu)建框架,用實現(xiàn)擴展細節(jié)。
//?創(chuàng)建訂單服務(wù)
public?interface?CreateOrderService?{
????public?void?createOrder(OrderBO?orderBO);
}
//?抽象創(chuàng)建訂單流程
public?abstract?class?AbstractCreateOrderFlow?{
????@Resource
????private?OrderMapper?orderMapper;
????public?void?createOrder(OrderBO?orderBO)?{
????????//?參數(shù)校驗
????????if?(null?==?orderBO)?{
????????????throw?new?RuntimeException("參數(shù)異常");
????????}
????????if?(OrderTypeEnum.isNotValid(orderBO.getType()))?{
????????????throw?new?RuntimeException("參數(shù)異常");
????????}
????????//?計算優(yōu)惠
????????discount(orderBO);
????????//?計算重量
????????weighing(orderBO);
????????//?退款支持
????????supportRefund(orderBO);
????????//?保存數(shù)據(jù)
????????OrderDO?orderDO?=?new?OrderDO();
????????BeanUtils.copyProperties(orderBO,?orderDO);
????????orderMapper.insert(orderDO);
????}
????public?abstract?void?discount(OrderBO?orderBO);
????public?abstract?void?weighing(OrderBO?orderBO);
????public?abstract?void?supportRefund(OrderBO?orderBO);
}
//?實現(xiàn)創(chuàng)建訂單流程
@Service
public?class?CreateOrderFlow?extends?AbstractCreateOrderFlow?{
????@Resource
????private?DiscountStrategyExecutor?discountStrategyExecutor;
????@Resource
????private?ExpressStrategyExecutor?expressStrategyExecutor;
????@Resource
????private?RefundStrategyExecutor?refundStrategyExecutor;
????@Override
????public?void?discount(OrderBO?orderBO)?{
????????discountStrategyExecutor.discount(orderBO);
????}
????@Override
????public?void?weighing(OrderBO?orderBO)?{
????????expressStrategyExecutor.weighing(orderBO);
????}
????@Override
????public?void?supportRefund(OrderBO?orderBO)?{
????????refundStrategyExecutor.supportRefund(orderBO);
????}
}
3.5.3 綜合應(yīng)用
上述實例業(yè)務(wù)和代碼并不復雜,其實復雜業(yè)務(wù)場景也不過是簡單場景的疊加、組合和交織,無外乎也是通過縱向做隔離、橫向做編排尋求答案。

縱向維度抽象出能力池這個概念,能力池中包含許多能力,不同的能力按照不同業(yè)務(wù)維度聚合,例如優(yōu)惠能力池,物流能力池,退款能力池。我們可以看到兩種程度的隔離性,能力池之間相互隔離,能力之間也相互隔離。
橫向維度將能力從能力池選出來,按照業(yè)務(wù)需求串聯(lián)在一起,形成不同業(yè)務(wù)流程。因為能力可以任意組合,所以體現(xiàn)了很強的靈活性。除此之外,不同能力既可以串行執(zhí)行,如果不同能力之間沒有依賴關(guān)系,也可以如同流程Y一樣并行執(zhí)行,提升執(zhí)行效率。
此時我們回到本文足球運動員管理系統(tǒng),如果采用縱向和橫向思維分析3.3.1足球先生選拔業(yè)務(wù)場景可以得到下圖:

縱向隔離出進攻能力池,防守能力池,門將能力池,橫向編排出前場、中場、后場、門將四個流程,在不同流程中可以任意從能力池中選擇能力進行組合,而不是編寫冗長的判斷邏輯,顯著提升了代碼可擴展性。
3.6 分層看架構(gòu)
系統(tǒng)架構(gòu)總體而言分為兩個層次,第一種層次是指本項目在整個公司位于哪一層次。持久層、緩存層、中間件、業(yè)務(wù)中臺、服務(wù)層、網(wǎng)關(guān)層、客戶端和代理層是常見的分層架構(gòu),大多數(shù)情況下業(yè)務(wù)需求最終會體現(xiàn)在服務(wù)層,不同的業(yè)務(wù)領(lǐng)域?qū)?yīng)不同的微服務(wù)。

第二種層次是指本項目內(nèi)部代碼的組織方式,一般可以分為接口層,訪問層,業(yè)務(wù)層,領(lǐng)域?qū)?,外部訪問層和基礎(chǔ)層。
(1) api
接口層:提供面向外部接口聲明和DTO
(2) controller
訪問層:提供HTTP訪問入口
(3) service
業(yè)務(wù)層:提供BO對象,領(lǐng)域?qū)雍蜆I(yè)務(wù)層都包含業(yè)務(wù),但是用途不同。業(yè)務(wù)層可以組合不同領(lǐng)域業(yè)務(wù),并且可以增加流控、監(jiān)控、日志、權(quán)限控制切面,相較于領(lǐng)域?qū)痈鼮樨S富
(4) domain
領(lǐng)域?qū)樱禾峁〥MO、VO、事件、DO和數(shù)據(jù)訪問,核心是根據(jù)領(lǐng)域進行分包,領(lǐng)域內(nèi)高內(nèi)聚,領(lǐng)域間低耦合
(5) dependency
外部訪問層:在這個模塊中調(diào)用外部RPC服務(wù),解析返回碼和返回數(shù)據(jù)
(6) infrastructure
基礎(chǔ)層:包含通用基礎(chǔ)功能,例如基礎(chǔ)工具,緩存工具,打印日志,消息發(fā)送

本文僅展開領(lǐng)域?qū)舆M行分析。領(lǐng)域?qū)雍诵氖前凑疹I(lǐng)域進行分包,并且提供DMO、VO、事件、DO和數(shù)據(jù)訪問,領(lǐng)域內(nèi)高內(nèi)聚,領(lǐng)域間低耦合。

3.7 接口看對接
一個接口代碼編寫完成后,那么這個接口如何調(diào)用,輸入和輸出參數(shù)是什么,這些問題需要在接口文檔中得到回答。
接口文檔生成有兩種方式,第一種方式是自動生成,例如使用Swagger框架,第二種方式是手工生成。自動生成的優(yōu)點是代碼即文檔,還具有調(diào)試功能,在公司內(nèi)部進行聯(lián)調(diào)時非常方便。但是如果接口是提供給外部第三方使用,那么還是需要手工編寫接口文檔。一個接口核心描述無外乎接口名稱、接口說明、輸入?yún)?shù)、輸出參數(shù),其它信息根據(jù)需要再增加。

4 文章總結(jié)
本文通過一個業(yè)務(wù)實例介紹了技術(shù)方案的七大維度:四色分領(lǐng)域、用例看功能、流程三劍客、領(lǐng)域與數(shù)據(jù)、縱橫做設(shè)計、分層看架構(gòu)、接口看對接。每個維度描述系統(tǒng)的一個側(cè)面,組合在一起最終描繪出整個系統(tǒng)。
在實際開發(fā)中如果需求不復雜,那么也不是七個維度都要體現(xiàn),而是根據(jù)實際情況取舍,能夠把方案說清楚即可,希望本文對大家有所幫助。
— 本文結(jié)束 —

●?漫談設(shè)計模式在 Spring 框架中的良好實踐
關(guān)注我,回復 「加群」 加入各種主題討論群。
對「服務(wù)端思維」有期待,請在文末點個在看
喜歡這篇文章,歡迎轉(zhuǎn)發(fā)、分享朋友圈


