最大化重用會使得可用複雜化。 —— 《Java 應用架構設計》

這個標題有點標題黨了,但是我覺得你能理解:爲什麼我會用這麼一個嚇唬人的標題?

文章起源於我對於模塊化、微服務、Serverless 以及單體應用幾種不同的架構模式的思考。而這其中的一個原因就是:人們經常從一個極端走另外一個極端。既然單體不好,那麼我們就要 FAAS 來替換單體;既然模塊化架構有各種問題,那麼我們應該回到大單體。

微架構

微架構是一系列架構風格的總稱,通過構建一系列責任單一的小型應用,再組合出一個複雜的大型應用系統。受限於不同的運行環境,它還具備有不同的特性。

對於運行操作系統的微架構來說,它具備了語言無關的特性,如微服務;對於運行在瀏覽器的微架構來說,它具備了易於集成遺留系統的優勢,如微前端;對於運行在移動操作系統虛擬機環境的微架構來說,它具備了應用可嵌入式的特質,如插件化。

微架構具體了一系列的好處:

  • 適應組織架構。即康威定律
  • 實現更多的價值。提升開發團隊的能力水平、組織級的 DevOps 提升;
  • 降低開發複雜性。解決單體應用邊界不清晰的問題
  • 改造遺留系統。採用絞殺者模式逐步重寫;或者直接封裝 API 不再添加新功能;
  • 減緩架構腐化。

從某種意義上來說,它符合人們所宣稱的幾點特性:可部署、可管理、可重用、可組合。

反過來,我們可以思考如果不在這幾種模式下的微架構是否合理?比如說,單一團隊維度大量的微服務、組件,不合理的技術劃分模塊和服務……

而實現上最容易出現微架構失效的原因就是:複用。

複用及其粒度

如果模塊過於細粒度和輕量級,那麼我們將面臨模塊和上下文依賴的爆炸。

複用可能導致的最大問題是,我們劃分的所有邊界都失效了。原本我們通過組織架構、變更頻率、權限等劃分的服務邊界,都在此時失去了作用。

代碼複用危機

過去,我們的複用方式是,模塊複用、包複用,整個系統間的關係相當的混亂。明明我們只是依賴於一個內部包裏的幾個函數 ,它們可能就是一兩百行的代碼,然而我們要引入一個幾 M 的包。出於同樣的目的,這個包又一次被其它系統使用了。

隨後,我們因爲自己的需求,改動了一次代碼,發現其它幾個系統都出了問題,便陷入了無窮無盡的開會和協調中。隨後,我們越來越傾向於不使用這個包,也不去改動這個包,這個包變成了一個 遺留的包 —— 沒有人敢改,沒有人想改。一直持續到其它項目重寫的那一天,這個包對於每個系統的人來說都是一個包袱。

而這一切可能只是我們調了其中的一個方法,複製過來 1 分鐘的事,卻要花費幾天的時間去解決。

於是,我們在幫助企業進行遺留系統重構和改造時,不得不依賴於 tequila 和 coca 這樣的工具,來完成對架構的可視化。而現在,我們開始考慮了微服務的複用。

微服務複用危機

同樣的,明明只是一個簡單的功能、API。故事是很相似的,我們選擇了已有的其它團隊的接口,這樣一來就不用花費時間寫這個接口了。一切都很完美。

然而,因爲 A 團隊的業務發生了一些調整,導致了接口的返回內容和我們預期的不一致。現在出現了預期之外的 bug,這個 bug 打亂了我們的節奏 —— 除了修復這個 bug,我們還要開會討論後續的接口變更問題,還有各種的扯皮。

雙方開始陷入了這場複用的惡夢中。

重複優於耦合

重用和複雜性是成正比的,越是關注於重用,則系統的複雜度會進一步上升。

有些人討厭設計模式的原因,在於濫用設計模式的人太多了。而設計模式是真的沒用嗎?它是一些解決問題的模式,只能套用在合適的問題上。而且隨着架構的變化,原有的模式可能不再適用。所以,我們總可能覺得前人用錯了模式,設計錯了架構。

也因此 DRY (Don't Repeat Yourself)這一原則並不總是適用的。反而,你可以認爲呢,它只在少數的時候是適用的。

組合優於繼承

繼承是實現代碼複用的有力手段,但它並非永遠是完成這項工作的的最佳工具。

這裏的組合指的不單單是代碼組織級的組合,對於微服務級別也是如此。

建立防腐層

嗯,就這麼簡單。

對於代碼級來說,我們複用三方接口時,一旦三方接口發生過變化,封裝便是我們的防腐方式。

對於微服務來說,我們複用三方接口時,一旦三方接口發生過變化,封裝、BFF 便是我們的防腐方式

最佳實踐

哦,它不是免費的 —— 因爲不會有最佳實實踐。

不過,我覺得你可以試着做這些事:

  • 提升重構能力 。當它們真的重複的時候,你就可以提取公共的部分;當它們真的不重複的時候,你可以內聯回去,再提取新的組件、函數、服務。
  • 避免過度設計 。我的意思是,不要在設計階段,過於草率地決定:它們就應該複用。
  • 階段性檢視 。當複用帶來複雜度時,重新梳理一下問題發生的原因。

事實上,看看標題就夠了。

相關文章