大型網(wǎng)站復(fù)雜業(yè)務(wù)持續(xù)重構(gòu)之道——全程領(lǐng)域建模實踐
![](http://image.woshipm.com/wp-files/img/104.jpg)
人物介紹:
Jack Chen ——“寵物商店”的首席架構(gòu)架構(gòu)師,擁有豐富的軟件設(shè)計與建模經(jīng)驗,但對新生事物持懷疑態(tài)度。
王總——“寵物商店”的總經(jīng)理,從美國留學(xué)后回國創(chuàng)立“寵物商店”網(wǎng)站。一路來唾手可得的成功讓他養(yǎng)成了固執(zhí)專橫的行事作風(fēng)。
Spark —— Jack Chen的大學(xué)同學(xué),一家商業(yè)軟件公司的高級咨詢顧問。最近熱衷于宣揚“領(lǐng)域驅(qū)動設(shè)計”的最佳實踐。
引子
就象大家所聽說過的那些神奇小子創(chuàng)業(yè)故事一樣,幾只從大西洋游回的海龜找到了一個偉大的idea——在互聯(lián)網(wǎng)上開辦在線商店銷售寵物。幸虧的是他們找到了投資者而且發(fā)展的很不錯。但是隨著時間的推移,當(dāng)初“完美”的技術(shù)架構(gòu)隨著越來越多的裝進(jìn)籃子的需求后變得不堪重負(fù)。作為公司首席架構(gòu)師的Jack Chen已經(jīng)被這幾個月“雞毛蒜皮”的需求折磨失眠好幾天啦。
Jack Chen周一一早就被興奮的王總給喊進(jìn)了辦公室,立即就被王總?cè)映鰜淼膇dea嚇傻了。
“我有一個很cool的想法,我們可以在線為寵物醫(yī)院提供在線預(yù)約的服務(wù)業(yè)務(wù)。而不僅僅是賣掉它們,你知道這意味著什么嗎?這是一個年產(chǎn)值上百億的市場?。。 薄?/p>
“可是王總,我們的系統(tǒng)不能支持這種非實物的服務(wù)預(yù)訂銷售,它可能對我們原有的網(wǎng)站形成巨大的沖擊,我們需要三個月的時間對這個業(yè)務(wù)進(jìn)行全方面的評估…”
Jack Chen立即就被氣勢洶洶的王總打斷了,“三個月的評估?我需要在兩個月內(nèi)就給我上線這個新業(yè)務(wù)。我們的投資人非常認(rèn)可我的idea,并要求我們立即把這個項目上線,它可能會幫助我們提高明年的IPO價格。你明白嗎? DO IT ASAP!”
評估
“好吧,也許這個該死的王胖子是對的。我們這個將技術(shù)與業(yè)務(wù)混在一起的亂攤子也是到了該整理整理的時候?!弊匝宰哉Z發(fā)了半小時牢騷后的Jack Chen終于恢復(fù)到正常狀態(tài)上來了,我想我應(yīng)該看看我們現(xiàn)在是什么樣子的,為了支持這個該死的“在線為寵物醫(yī)院提供在線預(yù)約的服務(wù)”的需求我們需要做出哪些改變。于是Jack Chen在白板上很快的就畫出了下面的Use Case圖來。
圖1 原寵物商店UseCase匯總圖
為了支持“在線預(yù)約”這種特殊的產(chǎn)品,它會影響到大部分的Use Case,具體列舉如下:
- 商品信息需要增加“預(yù)約時間”這個屬性,客戶在下訂單時會把它作為標(biāo)識一個預(yù)約的關(guān)鍵要素。
- “在線預(yù)約”是個虛擬的商品,它可不需要真的需要去檢貨和包裝發(fā)貨,如果真的那么做啦,我就太傻了。
- 每個寵物醫(yī)院每天都只能接受一定數(shù)量的預(yù)約,從這個概念上來說,它與實物商品有類似的庫存概念??墒俏以撛趺慈ケ磉_(dá)它們呢?
- 最要命的是:我真的要把這些所有受影響的Use Case都翻出來去讓它們支持虛擬物品的業(yè)務(wù)嗎?我怎么可能在2個月內(nèi)完成這些重構(gòu)?
銀彈
了無生趣的Jack Chen在王總的辦公室門口徘徊了N圈,還是沒有勇氣去迎接那一通狂風(fēng)暴雨般的中英文雙語版的羞辱謾罵。“也許事情是有轉(zhuǎn)機(jī)的,我好象在哪里聽說過有種銀彈可以解決這種系統(tǒng)重構(gòu)的問題的”?!霸撍?,誰把Spark送給我的《領(lǐng)域驅(qū)動設(shè)計》墊在顯示器下啦,他一直在向我布道這本書給他的項目帶來的種種神奇改變,也許我也可以試試它的威力”。
“好吧,Spark,我承認(rèn)你給推薦的書非常棒,你說的也很有道理。我讀了它,明白并一些概念——例如:領(lǐng)域分割 、Entity、Service、Value Object…,可我對于該如何去做還是一頭霧水。你能不能直接把你從重構(gòu)項目中獲得的最佳實踐直接分享給我呢?不然的話,周一王胖子是不會放過交不出答案的我的!”。讀完了這本書,Jack Chen覺得很有收獲,但又不知道怎么開始,打個電話給領(lǐng)域建模的先行者Spark也許真的是解決問題最快的方法。
“什么,這個問題說來話長?不要緊,我已經(jīng)在你家門口了,你同我慢慢說”,Jack Chen帶著星巴克咖啡+肯德基全家桶+久久鴨脖+諂媚的笑容出現(xiàn)在Spark家門口。
布道
Spark聽完了Jack Chen對于現(xiàn)狀及需求的描述之后,一幅氣定神閑的樣子訕訕地說出“這個很簡單嘛,你現(xiàn)在需要做的只是這樣一些事情:”
- 用大比例結(jié)構(gòu)對你的系統(tǒng)進(jìn)行領(lǐng)域劃分
- 找出這個需求影響的領(lǐng)域及對外接口
- 建立一個適合你們公司的領(lǐng)域驅(qū)動設(shè)計的技術(shù)框架
- 按照需求的緊急度來重構(gòu)各個領(lǐng)域的設(shè)計與編碼
下面我們就按照這個順序來實踐一下:
一、概要領(lǐng)域劃分
Jack Chen立即把自己之前畫的Use Case重畫了一遍,然后用希冀的眼神看著Spark等待著認(rèn)可?!澳愕腻e誤是過于看重Case或者操作者身份,領(lǐng)域的劃分不是基于功能或角色來進(jìn)行的,通常來說我們是將內(nèi)聚程度較高的Use Case歸到一個上下文中。盡量使得領(lǐng)域自閉程度較高,并擁有相同的業(yè)務(wù)語言環(huán)境。例如基于你的Use Case圖,我會畫出以下的領(lǐng)域”
圖2 寵物商店領(lǐng)域通道圖
通道圖是一個對業(yè)務(wù)領(lǐng)域建模非常有幫助的工具,它可以同時表達(dá)出執(zhí)行序列與分片的作用。
二、找出受影響的領(lǐng)域與接口
從領(lǐng)域的角度來看,只有商品對外暴露出來的接口是會影響到各個領(lǐng)域,需要優(yōu)先建立商品領(lǐng)域(ProductDomain)及讀取商品信息服務(wù)接口(GetProductService)來進(jìn)行重構(gòu)。
之外,在【圖2】 中用綠色標(biāo)識出來的Use Case是由于增加支持“在線預(yù)約”這種虛擬商品所需要進(jìn)行代碼重構(gòu)的部分。這部分工作如果工期比較緊,可以優(yōu)先使用模式的方式來進(jìn)行代碼重構(gòu),這樣也可以在之后更加容易用領(lǐng)域驅(qū)動設(shè)計的方法再次重構(gòu)。
三、建立技術(shù)框架
這一點,是《領(lǐng)域驅(qū)動設(shè)計》這本書沒有過多提及的內(nèi)容。這個需要結(jié)合你們公司的原來技術(shù)框架用最小化改造成本最大化收益的方式來建立領(lǐng)域驅(qū)動的技術(shù)框架。下面是一個可以廣泛使用的領(lǐng)域驅(qū)動的技術(shù)框架,可以在這之上增加更多的個性元素形成你公司自己的框架。
圖3 領(lǐng)域驅(qū)動設(shè)計參考技術(shù)框架圖
這個框架的各個元素基本上在 《領(lǐng)域驅(qū)動設(shè)計》一書中都可以找到對應(yīng)的解釋,但這里需要解釋一下我建立這個框架的個性理解:
- 領(lǐng)域?qū)ν猓撁?、AJAX、ESB調(diào)用)只暴露領(lǐng)域服務(wù),其它所有領(lǐng)域類都是包內(nèi)自閉的,對外不可見。
- 基礎(chǔ)倉庫的引入,基礎(chǔ)倉庫是一個抽象的倉庫,它封裝了大量常用工具方法、業(yè)務(wù)對象生命周期維護(hù)(實體OR映射、DAO調(diào)用)、外部接口調(diào)用??梢越档蜆I(yè)務(wù)倉庫不必要的重復(fù)編碼與復(fù)雜性。業(yè)務(wù)倉庫是繼承基礎(chǔ)倉庫的子類。
- 基礎(chǔ)設(shè)施的引用,基礎(chǔ)設(shè)施是用來承載引用非領(lǐng)域調(diào)用的樁,我們在使用領(lǐng)域驅(qū)動設(shè)計的時候往往是從一個舊的系統(tǒng)重構(gòu)開始。這時我們不可能要求所有的業(yè)務(wù)子系統(tǒng)相互調(diào)用都通過Domain Service調(diào)用,這時我們可以通過Infrastructure優(yōu)美的把調(diào)用封裝在業(yè)務(wù)倉庫的業(yè)務(wù)方法內(nèi)。
四、重構(gòu)受影響領(lǐng)域的設(shè)計與編碼
圖4 重構(gòu)后的商品詳情頁類圖
Spark以商品詳情頁這個Use Case為例展示了以領(lǐng)域驅(qū)動設(shè)計的重構(gòu)類圖:
- 增加行為表ProductExt用于存儲商品的擴(kuò)展信息,如預(yù)約時間段、預(yù)約醫(yī)院。并為表建立一一對應(yīng)的實體Entity。
- 基礎(chǔ)倉庫Repository通過Infrastructure中的DAO封裝了對實體的操作,如create()、update()、delete()、findById()、findList()
- 商品業(yè)務(wù)倉庫ProductRepository擴(kuò)展了基礎(chǔ)倉庫,客戶程序可以用productId為參數(shù),通過ProductVo.getProduct()方法獲得商品詳細(xì)信息的業(yè)務(wù)實現(xiàn),由于業(yè)務(wù)倉庫的的公開方法對外返回的都是Value Object,因此不會直接暴露Entity類型給客戶程序。
- GetProductService服務(wù)類通過invoke()服務(wù)方法 對外(商品詳情頁面)提供服務(wù),它通調(diào)用業(yè)務(wù)倉庫中的業(yè)務(wù)方法,并將接口規(guī)格化。
- 事務(wù)配置在DomainService的invoke()方法上,即事務(wù)控制以Use Case為粒度進(jìn)行控制。
尾聲
在Spark的幫助下,Jack Chen成功的脫離了困境?,F(xiàn)在他正在公司里積極推行自己的領(lǐng)域驅(qū)動設(shè)計框架,他們公司的網(wǎng)站正在以每三周一次的重構(gòu)速度快速迭代演進(jìn)。他象Spark一樣,成為了一個領(lǐng)域驅(qū)動的布道者。
來源:InfoQ
- 目前還沒評論,等你發(fā)揮!