給COLA 做減法:應用架構中的「彎彎繞設計」

2020-12-23 阿里技術

阿里妹導讀:COLA 的主要目的是為應用架構提供一套簡單的可以複製、可以理解、可以落地、可以控制複雜性的」指導和約束"。在實踐中作者發現 COLA 在簡潔性上仍有不足,因此給 COLA 做了一次「升級」,在這次升級中,沒有增加任何新的功能,而是儘量多刪減了一些概念和功能,讓 COLA 更簡潔有效。

最近,同事告訴我,COLA 作為應用架構,已經被選入阿里雲的 Java 應用初始化的應用架構選項之一。

This is really something,於是,在這個裡程碑節點上,我開始回過頭來,重新審視COLA 一路走來的得與失。

COLA 作為一種架構思想無疑是成功的。但是作為框架,個人感覺有點雞肋之嫌。特別是在簡潔性上做的不好,感覺做了不少畫蛇添足的事情。

試想一下,有些功能我作為作者都很少去使用,我實在想不到,它為什麼還有存在的理由。

基於上面的思考,我做了這一次 COLA 2.0 到 COLA 3.0 的升級。在本次升級中,我沒有增加任何新的功能,而是儘量多刪減了一些概念和功能。讓 COLA 可以更加純粹的 focus 在應用架構上,而不是框架支持和架構約束上。

支持我做這些決策的背後原因只有一個——奧卡姆剃刀原理。

奧卡姆剃刀原理

奧卡姆剃刀原理,是指如無必要,勿增實體(Entities should not be multiplied unnecessarily),即「簡單有效原理」。正如奧卡姆在《箴言書注》2 卷 15 題說「切勿浪費較多東西去做,用較少的東西,同樣可以做好的事情。」

在具體的應用過程中,我們可以遵循以下原則去做事情:

「如果同一個現象有 n 種理論,最簡單的那個便是最正確的。能用 n 做好事情,那就不要有第 n+1 個動作。」

比如,《皇帝的新衣》的皇帝到底穿沒穿衣服呢?如果你在現場,你很有可能就是大臣之一。

如果懂得了奧卡姆剃刀原理,可以用邏輯手段,判斷誰是真理。

第一種邏輯如下:假設皇帝是真的穿了衣服→假設愚蠢的人看不見→假設你就是愚蠢的人→所以你沒看見皇帝穿衣服。

第二種邏輯如下:假設皇帝沒穿衣服→所以你沒看見皇帝穿衣服。

同樣看見光身子的皇帝,第二種解釋簡單明了。而第一種解釋,可能正因為它是錯誤的,就需要更多假設來補救漏洞,就像說謊圓謊一樣。

真相不需要偽裝掩飾,簡單明了。

再比如,地心說和日心說,託勒密的地心說模型是一個本輪均輪模型。人們可以按照這個模型,定量計算行星的運動,據此推測行星所在的位置。

到了中世紀後期隨著觀察儀器的不斷改進,人們能夠更加精確地測量出行星的位置和運動,觀測到行星實際位置與這個模型的計算結果存在偏差,一開始還能勉強應付,後來小本輪增加到八十多個,卻仍然不能精確地計算出行星的準確位置。

1543 年,波蘭天文學家哥白尼在臨終時發表了一部具有歷史意義的著作——《天體運行論》。這個理論體系提出了一個明確的觀點:太陽是宇宙的中心,一切行星都在圍繞太陽旋轉。該理論認為,地球也是行星之一,它一方面像陀螺一樣自轉,一方面又和其他行星一樣圍繞太陽轉動。

哥白尼的計算不僅結構嚴謹,而且計算簡單,與已經加到八十餘個圈的地心說相比,哥白尼的計算與實際觀測資料能更好地吻合。因此,地心說最終被日心說所取代。

設計中的彎彎繞

深入考察一下,我們系統中,類似於「地心說」這樣的彎彎繞的設計,實在是不在少數。

從系統架構的角度看,有些彎彎繞是因為系統邊界劃分不合理,導致職責不清,依賴混亂。

從應用架構的角度看,有些彎彎繞是因為過度設計,為了追求所謂的靈活性和可擴展性,使用了不恰當的設計。導致本來可以直觀呈現的代碼邏輯,被各種包裝,各種隱藏,各種轉發.... 無形中極大的阻礙了代碼的可讀性和可理解性,增加了維護成本。

舉個例子,我看過無數的業務系統,喜歡拿業務流程編排說事情。因此,在業務系統中,可以看到各種五花八門的「彎彎繞設計」。

比如,在一個業務系統中,我看到了如下的 pipeline 設計。這個設計的本質是說把一個複雜的業務操作進行結構化拆解為多個小的處理單元。

拆解是正確的,但是這種處理方式顯然不夠「奧卡姆」(關於更多結構化分解的內容,可以看我的另一篇文章:如何寫複雜業務代碼?)。作為維護人員,進入「入口函數」後,還要去查資料庫,然後才能知道哪些組件被調用了,太繞了,不夠直觀,也不簡潔。

同樣的邏輯,按照下面的方式寫不香嗎?

publicclassCreateCSPUExecutor{@Resourceprivate InitContextStep initContextStep;@Resourceprivate CheckRequiredParamStep checkRequiredParamStep;@Resourceprivate CheckUnitStep checkUnitStep;@Resourceprivate CheckExpiringDateStep checkExpiringDateStep;@Resourceprivate CheckBarCodeStep checkBarCodeStep;@Resourceprivate CheckBarCodeImgStep checkBarCodeImgStep;@Resourceprivate CheckBrandCategoryStep checkBrandCategoryStep;@Resourceprivate CheckProductDetailStep checkProductDetailStep;@Resourceprivate CheckSpecImgStep checkSpecImgStep;@Resourceprivate CreateCSPUStep createCSPUStep;@Resourceprivate CreateCSPULogStep createCSPULogStep;@Resourceprivate SendCSPUCreatedEventStep sendCSPUCreatedEventStep;publicLong create(MyCspuSaveParam myCspuSaveParam){ SaveCSPUContext context = initContextStep.initContext(myCspuSaveParam); checkRequiredParamStep.check(context); checkUnitStep.check(context); checkExpiringDateStep.check(context); checkBarCodeStep.check(context); checkBarCodeImgStep.check(context); checkBrandCategoryStep.check(context); checkProductDetailStep.check(context); checkSpecImgStep.check(context); createCSPUStep.create(context); createCSPULogStep.log(context); sendCSPUCreatedEventStep.sendEvent(context);return context.getCspu().getId(); }}

這種寫法簡單直觀,易維護,與前一種方式相比,具有同樣的組件復用性。符合奧卡姆剃刀的精神,相比較而言,前面那種彎彎繞設計,雖然看起來有點設計感,帶來了一點點 OCP 的好處。但是無端增加了理解和認知成本,孰優孰劣,不難分辨。

COLA 3.0 升級

做了這麼長的鋪墊,終於到了批鬥 COLA 中「彎彎繞設計」的時候了。

去掉 Command

在 COLA 的初始階段,因為受到 CQRS 的影響,於是想到了使用命令模式來處理用戶請求。設計的初衷是想通過框架,一方面強制約束 Command 和 Query 的處理方式,另一方面把 Service 裡面的邏輯,強制拆分到 CommandExecutor 中去,防止 Service 膨脹過快。

和上面介紹過的 pipeline 設計類似,這種設計有點繞,不夠直觀,如下所示:

publicclassMetricsServiceImplimplementsMetricsServiceI{@Autowiredprivate CommandBusI commandBus;@Overridepublic Response addATAMetric(ATAMetricAddCmd cmd){return commandBus.send(cmd); }@Overridepublic Response addSharingMetric(SharingMetricAddCmd cmd){return commandBus.send(cmd); }@Overridepublic Response addPatentMetric(PatentMetricAddCmd cmd){return commandBus.send(cmd); }@Overridepublic Response addPaperMetric(PaperMetricAddCmd cmd){return commandBus.send(cmd); }}

看起來還挺乾淨的,可是 ATAMetricAddCmd 到底是被哪個 Executor 處理的呢,不直觀。我還要去理解 CommandBus,以及 CommandBus 是如何註冊 Executor 的。無形中增加了認知成本,不好。

既然這樣,為何不用奧卡姆剃刀把這個 CommandBus 剔除呢。如下所示,去除 CommandBus 之後,代碼是不是直觀了很多,唯一的損失是我們會失去框架層面提供的 Interceptor 功能,然而,Interceptor 正是我下一個要動刀的地方。

publicclassMetricsServiceImplimplementsMetricsServiceI{@Resourceprivate ATAMetricAddCmdExe ataMetricAddCmdExe;@Resourceprivate SharingMetricAddCmdExe sharingMetricAddCmdExe;@Resourceprivate PatentMetricAddCmdExe patentMetricAddCmdExe;@Resourceprivate PaperMetricAddCmdExe paperMetricAddCmdExe;@Overridepublic Response addATAMetric(ATAMetricAddCmd cmd){return ataMetricAddCmdExe.execute(cmd); }@Overridepublic Response addSharingMetric(SharingMetricAddCmd cmd){return sharingMetricAddCmdExe.execute(cmd); }@Overridepublic Response addPatentMetric(PatentMetricAddCmd cmd){return patentMetricAddCmdExe.execute(cmd); }@Overridepublic Response addPaperMetric(PaperMetricAddCmd cmd){return paperMetricAddCmdExe.execute(cmd); }}

去掉 Interceptor

當時設計 Interceptor,是因為有 CommandBus 作為基礎,為了更好的利用命令模式帶來的好處,便添加了 Interceptor 功能。其本質是一個 AOP 處理。

鑑於 Spring 的 AOP 功能已經很完善了,這個設計也是有點雞肋。事實證明,大家在使用 COLA 框架的時候,很少會使用 Interceptor,包括我自己也是一樣。既然如此,剔除也罷。

去掉 Convertor、Validator、Assembler

關於命名的重要性,這裡就不贅述了。當時想著是否能從框架層面,規範一下一些常用功能的命名。但是在實際使用中,發現這個想法也是有些過於理想化了。

我記得,在團隊實踐 COLA 的初期,還經常為什麼是 Convertor(轉換器),什麼是 Assembler(組裝器)的事情,爭論不休。

後面我仔細想了想,命名雖然很重要,但其作用域最多也就是一個團隊規範,你校驗器是叫 Validator 還是 Checker 並沒有什麼本質區別,團隊自己定義就好了。嘗試從框架層面去解決團隊約定問題,其效果不會太好,因此也果斷揮刀剔除。

類掃描優化

業務身份和擴展點的思想,是 TMF 的核心理念,也是阿里業務中臺的進行多業務支持的核心方法論。

COLA 致力於提供一種輕量級的擴展實現方式,因此該功能在奧卡姆的屠刀下得以保存。因為 COLA 的擴展點設計是借鑑了中臺的 TMF,因此在前面的設計中,其類掃描方案是直接照搬 TMF 的做法。

實際上,TMF 的類掃描方案對 COLA 來說有點多餘。因為 COLA 本身就是架設在 Spring 的基礎之上,而 Spring 又是建立在類掃描的基礎之上。因此,我們完全可以復用 Spring 的類掃描,沒必要自己寫一套。

在原生的 Spring 中,至少有 3 種方式可以獲取到用戶自定義 Annotation 的 Bean,最簡潔的是通過 ListableBeanFactory.getBeansWithAnnotation 方法,或者使用 ClassPathScanningCandidateComponentProvider 進行掃包。

在這次改版中,我選用的是 getBeansWithAnnotation 方法,主要是為了獲取 @Extension 的 Bean,用來實現擴展點功能,廢棄了原來的 TMF 類掃描實現。

總結

觸發這次升級的動機,主要是因為,自己在實踐 COLA 的過程中,的確發現有些華而不實的功能。在 COLA 作為阿里雲的基礎應用架構,其影響力越來越大的時候,我有責任給到大家一個正確的引導——去偽存真,簡潔有效,而不是引入更多的複雜度。

實際上,COLA 是由兩部分組成的:

一方面 COLA 是一種架構思想,是整合了洋蔥圈架構、適配器架構、DDD、整潔架構、TMF 等架構思想的一種應用架構。

在這次升級中,架構思想部分基本沒有變化,唯一一點是因為去除了 Command 概念,因此 CQRS 也成了可選項,而不再是一種強要求。

另一方面 COLA 也是框架組件,通過這次升級,我使用奧卡姆剃刀砍掉了絕大部分的組件能力,僅僅保留了擴展點功能。其用意是不希望 COLA 作為框架給到應用開發者太多的約束,這不符合簡單有效的風格。

所以,總結下來,與其說這是一次升級,不如說它是功能「降級」,是在做減法。

但我相信,減法可以讓 COLA 更加符合奧卡姆精神,幫助 COLA 輕裝上陣,走的更遠。

COLA 開源地址:https://github.com/alibaba/COLA

阿里雲 Java 應用腳手架

start.aliyun.com 是基於 Spring-initializr 實現的工程腳手架生成平臺,開發者們只需要添加一些註解和少量配置,就可以快速搭建分布式應用系統,它使用更親切的中文,也不會有網絡延遲問題,最重要的是提供更多本地化的組件依賴。

相關焦點

  • 什麼是微內核架構設計?
    阿里妹導讀:作為一名Java程式設計師,相信同學們都聽說過微內核架構設計,也有自己的理解。那麼微內核是如何被提出來的?微內核在作業系統內核的設計中又有什麼作用?本文從插件化(Plug-in)架構的角度來詮釋微內核架構設計,通過微內核架構和微服務架構的對比,分享其對微服務設計的參考意義。
  • 「懶貓創意空間設計」劉彎彎:做有溫度的設計,期待中原改造家
    房子不是冰冷的空間載體,要做就要做有溫度的空間設計,來自深圳的「懶貓創意空間設計」,是一家以理性和創新為理念的設計公司,解決空間設計中面臨的各種問題。您是怎樣理解設計這個職業的?劉彎彎:從小喜歡畫畫,接觸到設計後覺得把想像中的畫面變為現實是一件很有成就感的事情;設計除了要表達自己還有很多從外界來的豐富有趣的體驗,想要做好設計師這個職業一定是熱愛生活,敢於嘗試的並且堅持下去的人。
  • 如何保證軟體應用系統架構設計結果的可擴展性和可重用性(上篇)
    軟體項目實訓及課程設計指導——如何保證軟體應用系統架構設計結果的可擴展性和可重用性(上篇)1、良好的可重用性軟體系統架構設計結果的主要體現可重用性的軟體應用系統的系統架構設計結果主要體現在如下兩個方面——本項目的系統架構設計的結果是可重用的和在本項目的系統架構設計中重用成熟的系統架構設計方案
  • Netflix 微服務架構設計解析
    Netflix 還是微服務架構背後的首批主要推動者之一。微服務鼓勵關注點分離來解決單體軟體設計存在的問題。在這種架構中,大型程序通過模塊化和獨立的數據封裝被分解為許多較小的軟體組件。微服務還通過水平擴展和工作負載分區來提升可擴展性。採用微服務後,Netflix 工程師可以輕鬆更改任何服務,從而加快部署速度。
  • 技術中臺之移動平臺安全架構設計
    界面劫持界面劫持通俗點講就是做了一個和目標App完全一樣的頁面,通過技術手段讓目標app閃退,啟動木馬頁面,一旦用戶輸入帳號密碼,你的信息就會被竊取。1.3移動安全問題之開發工具攻擊大家還記得2015年的Xcode Ghost病毒嗎?該病毒波及眾多產品,其中不乏大公司的知名應用,也有不少金融類應用,還有諸多民生類應用。
  • 軟體架構設計:軟體質量屬性、架構風格的案例
    在項目之初,公司的系統分析師對該集成開發環境的需求進行了調研和分析,具體描述如下:a.需要同時支持該廠商自行定義的應用程式語言的編輯、界面可視化設計、編譯、調試等模塊,這些模塊產生的模型或數據格式差異較大,集成環境應提供數據集成能力。集成開發環境還要支持以適配方式集成公司現有的應用模擬器工具。
  • 15 年架構設計經驗:我眼中的那些優秀架構師
    所以我面試的時候,就從他做過架構設計的項目出發,摘了幾個具體的點去深度溝通。 然而,當我真的圍繞「架構師」職責去考察時,卻發現,他對「架構師」的理解,還停留在接到需求後,依據產品設計給出實現的階段。對於接下來的模塊分解、代碼重構、技術選型、性能優化等方面,雖然他有所了解和接觸,但實在太過皮毛,缺乏體系化的理解。
  • 多元化發展非良方 家電企業在逆境中要「做減法」
    面對當前的市場現狀,筆者認為,對於眾多中國家電企業來說,自救的最佳手段就是要"做減法",清理掉沉重的包袱,輕裝上陣,實現從量變到質變的轉化。事實上,這也與中央對經濟發展的定調不謀而合,那就是:我國經濟發展已進入"新常態","增長速度要從高速轉向中高","發展方式要從規模速度型轉向質量效率型"。艱難時刻更要做減法家電製造是重資產行業,產業鏈集中,技術迭代、設備更新等耗資高,經營負擔重。
  • 如何用「做減法」的審美趣味欣賞香水?
    今天,讓我們繼續跟Della一起,探索香水與建築的聯繫,用「做減法」的審美趣味來欣賞香水。講述 | 莊卉家(Della Chuang)《感覺香水:香奈兒和她的藝術家朋友圈》嗅覺與建築我們是可以通過嗅覺,來體驗建築的。
  • Coca-cola,Pepsi coke是什麼意思?
    Coca-cola,Pepsi coke是什麼意思?把英語Coca-cola,Pepsi coke說成英語Coca-cola,Pepsi coke是什麼意思?有多少英語學習者見到英語Coca-cola,Pepsi coke時第一句話就是說成中文「可口可樂和百事可樂」的?我經常說「英語思維就是英語口語」,只不過「英語思維」是一種「無聲口語」,一旦把「無聲思維」大聲「說」出來,變成「有聲思維」,它就變成了「英語口語」。Coca-cola or Pepsi coke?1.
  • 軟體項目實訓及課程設計指導——可擴展和可重用是架構設計的目標
    希望讀者多深入思考這些問題,一個不合理的軟體應用系統的系統架構設計結果的主要表現如下:1)缺乏靈活性——很難在軟體應用系統中添加新的功能模塊,因為對軟體應用系統中的每一處的修改就會影響到軟體應用系統中其他多個不同的功能模塊,產生「牽一髮而動全身」的影響效果,該軟體應用系統的體系架構設計是僵化的!
  • 軟體項目實訓及課程設計指導——如何實現面向服務的系統架構設計
    )其實就是一種新型的軟體應用系統的系統體系架構設計形式,在基於SOA架構設計思想所開發實現的企業級軟體應用系統中,軟體應用系統中的各個業務功能的程序模塊是由一些鬆散耦合併且具有統一接口定義的服務組件相互組合而構建的。
  • 偶爾也要給自己家做減法
    考量屋主夫妻預算,在不變動格局下由擅長日式簡約風格的CH Interior Design操刀改造,剔除老屋原本繁複表相裝飾,用減法美學,只保留必要單品,讓居住者重拾家的掌控權,並透過大量質樸木質演繹場域使用初衷,輕鬆營造舒壓療愈氛圍。
  • Jenkins 多環境 CI/CD 架構設計
    ------build-result 構建產物(編譯後的結果)--------test----------系統標識 ----------應用名------temp-file 臨時文件,job執行過程中產生的文件--------builder-history
  • 真正高級的人生,是不斷做減法
    文 |晴風圖 |網絡曾在一本書上看到這樣的一句話:這世上,最聰明的人其實是懂得給生活做減法的人。現實中,很多人不願給生活做減法,只會一次又一次地給生活做加法,最後我們的家亂七八糟,我們的生活亂七八糟,我們的思緒亂七八糟。而真正高級的人生,其實就是不斷做減法。
  • 淺談抗震設計在房屋建築結構設計中的應用
    抗震設計在房屋建築結構設計中的應用(1)嚴格遵守地震設防標準設計由於是基於一定事實作為依據來制定地震設防標準,因此在具體房屋建築設計時,需要針對不同房屋的抗烈度要求,確保房屋抗震性與當地地震設防要求相符。
  • 為空間做「減法」,提升定製居家狀態
    其實很多時候,設計師需要為屋主做減法,在快節奏的生活狀態下,利用設計去提高房屋的使用效率,享受更快捷與便利的生活狀態,把節約下來的時間去享受生活。客廳櫃既補充了客廳區收納空間,也豐富了設計師要給空間做減法的意義,同時更是實現了功能性與美觀性的共存。
  • 如何使用BLoC架構開發Flutter應用
    BLoC架構(您可以通過將BLoC庫集成到項目中來使用)由Felix Angelov創建,並於2019年在Google I / O中引入。 注意,BLoC是一種模式,而不是架構本身。您仍然需要根據DDD,MVVM或Clean之類的架構來控制應用程式中的數據。 BLoC幫助組織數據流,如今它已成為Flutter開發中最受歡迎的模式。
  • 解析全新一代電子電氣架構設計得重要性
    Whydell表示,「這和你手機裡的處理器很相似,主要體現在你可以自己選擇要給自己的手機安裝哪些應用程式。類似地,我們可以說採埃孚將提供處理器,而廠商則能決定到底要選哪些『應用程式』。他們可以從我們這裡購買所需的功能,也可以自己研發或讓一些專業的軟體公司進行開發。」AUTOSAR(汽車開放系統架構)聯盟的主要目標之一,是在系統內與系統之間提供更大的靈活性。
  • 紅帽OpenShift通過架構、應用和基礎架構創新,讓混合雲無處不在
    紅帽2020年發布的《企業開源現狀報告》進一步指出市場對混合雲的需求,其中63%的受訪者表示其目前已構建混合基礎架構,而尚未構建混合雲基礎架構的受訪者中超過半數表示其計劃在兩年內部署。紅帽將Kubernetes視為混合雲計算的基石,能夠以大家熟知的工具與服務提供一個涵蓋裸機伺服器、虛擬環境以及私有和公有雲環境的通用平臺。