絞殺者模式 (一)

背景

隨著系統老化、開發工具逐漸落伍、bug 堆積,項目會變得及難維護。所以“腐爛”是所有遺產項目不可避免的一環。一般企業基本不會再去碰遺產項目,但是現代很多公司卻喜歡另辟蹊徑——每兩三年用新技術重構一遍代碼。眾所周知,遺產代碼很難改動,那它們這么做的自信來自何處?

故事要從一個叫 Martin Fowler 的老頭說起。有一次,他在熱帶雨林里旅游,無意間發現了一種叫“絞殺者藤蔓”的植物:

絞殺者藤蔓

絞殺者藤蔓會沿著其他樹干一路向上生長,以獲取光線資源;隨著藤蔓的不斷生長,之前的寄生樹會被這種植物整個包裹,并隨著陽光資源的枯竭而死亡;最后樹木腐爛而逝,但是原地卻留下了一顆樹狀的巨大藤蔓。

絞殺過程

Martin 老頭回去后就把這個故事寫在了博客上,并提出了一個叫“絞殺者模式”的策略:通過逐步重構單體應用(而不是推到重來的方式),逐漸構建出一個新的應用程序。這也成為了后來開發人員對遺產系統進行現代化改造的基本方針。

絞殺者模式

講完植物,我們從實現策略上探討一下“如何絞殺”;絞殺的大體操作如下:

  1. 創建一個門面攔截后端遺產系統的請求
  2. 通過一定的規則,門面會將特定請求分別路由到新舊系統上——反向代理
  3. 保留遺產系統的原有功能,同時在新系統中重寫舊的模塊,并逐漸地將請求傾斜到新系統中;遷移過程中,由于門面的隔絕,消費者照常使用現有功能,并不會有任何后臺重構的感知
  4. 完成遷移后,所有請求將路由到新系統上,遺產系統被“絞死”
絞殺者模式

絞殺者的好處是:保留了遺產系統的代碼,以平滑遷移的形式朝著新應用邁進;這確保了每一步前進都有回退的機會。漸進遷移的時間可能會很長,甚至可以永久性的保持部分遺產系統的功能。

實操

OK,理論很簡單,實操中會有各種各樣的問題,重構過程通常要持續數月甚至數年。僅僅宣稱“通過絞殺者模式可以實現系統遷移”,這種話是沒用的:一想到你要動所有代碼,大家立馬就慌了,決策者(不管懂不懂技術)都不會如此草率地行動。所以最好能有更細節的拆分步驟。所謂的“拆分”,可以從表現層、服務層、持久層等等方面入手;只要記住從最簡單的部分開始,當顯現出價值后,就可以持續加速改造了。

通常來說,表現層的拆分是最簡單的,比如將 JSP 或 tymeleaf 這種后端渲染框架拆成 restful api + react 的形式——動靜分離——就可以很快出貨。我們看看具體的改造過程。

反向代理

實現絞殺者模式的第一步自然是加個門面啦。我能想到最最最簡單的門面就是:專職的反向代理工具 nginx 了。反向代理我之前寫過一篇文章,大家可以點這里查看。務必確保團隊成員已經有了最基礎的重定向知識;第一步就遇到認知障礙,這事就非常令人沮喪了。

言歸正傳,通常來說,第一步的反向代理無須對請求做任何處理,只要簡單穿透:

Reverse Proxy

如果使用 nginx 的話,配置也很簡單,把根路勁代理到遺產系統的服務上即可:

# nginx.conf

server {
  location / {
    proxy_pass https://legacy.com;
    ...
  }
}

遷移功能

一旦 HTTP 反向代理就緒,我們就可以開始抽取功能代碼了。當然,遷移方式上又有好多策略,比如分離表現層、重構數據庫、提取領域服務等等;由于篇幅限制,本期只講最簡單的表現層分離。

以比較原始的 JSP 應用為例,通常可以將 JSP 做“動靜分離”的重構:

  • 動態部分:即原先綁定在 JSP 上的 model 數據。將它們以 Rest API 的形式暴露出去,JSP 以下的業務邏輯保持不動
  • 靜態部分:即 JSP 的 HTML 模版(UI)部分。這部分以現代前端框架——如 reactJS——重新實現。重寫的 UI 代碼全部放到新的系統中,遺產系統中的 JSP 代碼保持不動
Split

題外話,利用新技術棧實現功能模塊后,對應的 CI/CD 也應在第一時間跟上;一系列 UI 測試也要在第一行代碼起開始編寫。不然幾個月后,你會發現新系統并不會比老系統強健多少。

重定向

如果 CI 配置得當,react 代碼的修改從 Merge PR 到完成新系統模塊更新——現階段事實上只有靜態文件——應該可以控制在幾分鐘內完成。

新系統怎么集成呢?我們需要把瀏覽器請求的特定資源重定向到新系統上:

Redirect

新系統需要一個新的代理路勁(如/modern/)以便與舊系統區分,代理配置上加個 location 即可:

# nginx.conf

location /modern/ {
  ...
  proxy_pass http://modern.com/;
}

當然,這時候新系統依舊不會起任何效果的。原因也很簡單,重構初期,html 入口基本都在遺產系統里,除非代碼里 hard code /modern/ 相關請求,否則 UI 不會與新系統產生關聯——當然這種修改是我們不愿意看到的。

怎么辦呢?這里講一個小技巧,利用 nginx 給所有的 html 注入一條指向 modern 的 js,配置大體如下所示:

# nginx.conf

location / {
  proxy_pass https://legacy.com;
  sub_filter '</body>' '<script type="module" src="/modern/app.react.js"></script></body>';
  sub_filter_once on;
}

只要給遺產系統的 html 注入 app.react.js,所有 UI 相關操作就可以在新系統代碼內修改了;而遺產系統的 JSP 無需做任何改動。

數據綁定

上面提到了“動靜分離”,那數據和模版分離后,兩者怎么互相綁定呢?可能有些朋友會有疑惑,這里補充說明一下,react.js 通常利用異步請求 Rest API 的形式獲取數據,并在它自己的模版上實現綁定。這是所謂的MVVM 模式,也是現代化前端框架優于 JSP 框架的重要原因。

我們看看加了 react 重構后的頁面請求順序:

  1. 瀏覽器提請頁面
  2. 由于 nginx 默認設置,請求都會路由到遺產系統上
  3. JSP 生成 html 后返回頁面
  4. nginx 會給所有 html 返回注入腳本標簽<script type="module" src="/modern/app.react.js"></script>
  5. 瀏覽器解析到上述標簽后,再發起/modern/關聯的 js 的請求
  6. 這個請求會被路由到新系統上
  7. 新系統返回 app.react.js
  8. 瀏覽器執行上述 js,發現有 Rest API 的請求,于是再發起數據請求
  9. 數據請求也是走默認配置路線,被 nginx 指向遺產系統
  10. 遺產系統返回 Rest API 的 JSON 數據
動靜分離

最后,就是在瀏覽器上 react 框架自動實現數據綁定了。相較于 JSP 那種一股腦后端渲染并返回一個巨大的 HTML,“動靜分離”的模式在過程中相對復雜一點,新入門的朋友可能要增加點認知成本了。但最終體現在代碼上的話,其實更簡單;這東西寫過一兩個頁面就能感覺到了,我這里不深入講解了。

小結

OK,UI 重構的基本框架大致搭建完工了,之后就是根據特定業務逐步地遷移各個前端模塊。
推薦在初始階段可以將 react 當 jQuery 用——就是當 lib 用啦:通過 selector 找到遺產代碼的特定 DOM,重構之;一個頁面完工后,再修改 nginx.conf,用以劫持新頁面到 modern 系統。如果進度順利,在第一個頁面完工后就可以體現出重構效果了——肉眼可見的加載速度。

前端遷移完后,就是后端服務的拆分了,我會在《絞殺者模式(二)》里進一步講解,敬請關注!

碎言碎語

我曾參與過一個單體架構的遺產項目,積年累月堆積了幾萬個 bug;然而,作為開發人員平均每人每周的 bugfix 量僅為 1(時常還能引入點新 bug)。我想不出任何方式能在這個項目徹底玩完前減少一些賬面的 bug 數,所以就專程前往廠里的一位領導干部那里請示。他告訴我,“重新定義 bug,那些 bug 就沒了!”

聽完后,豁然開朗:Bug 是絕對修不完的,所以不要再糾結每周修 1 個 bug 或是 2 個 bug 這種問題了。分一點時間出來——比如 20%的人力——遷移產品,在遷移的過程中逐步捋順業務邏輯,當遷移完工后,遺產系統的 bug 就不再是 bug 了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容