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

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

原文連接:點擊這裏github

image.png

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

image.png

This is really something,因而,在這個里程碑節點上,我開始回過頭來,從新審視COLA 一路走來的得與失。架構

COLA 做爲一種架構思想無疑是成功的。可是做爲框架,我的感受有點雞肋之嫌。特別是在簡潔性上作的很差,感受作了很多多此一舉的事情。框架

試想一下,有些功能我做爲做者都不多去使用,我實在想不到,它爲何還有存在的理由。ide

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

支持我作這些決策的背後緣由只有一個——奧卡姆剃刀原理。優化

奧卡姆剃刀原理

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

在具體的應用過程當中,咱們能夠遵循如下原則去作事情:阿里雲

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

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

若是懂得了奧卡姆剃刀原理,能夠用邏輯手段,判斷誰是真理。

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

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

真相不須要假裝掩飾,簡單明瞭。

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

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

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

image.png

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

設計中的彎彎繞

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

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

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

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

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

image.png

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

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

public class CreateCSPUExecutor {
    @Resource
    private InitContextStep initContextStep;

    @Resource
    private CheckRequiredParamStep checkRequiredParamStep;

    @Resource
    private CheckUnitStep checkUnitStep;

    @Resource
    private CheckExpiringDateStep checkExpiringDateStep;

    @Resource
    private CheckBarCodeStep checkBarCodeStep;

    @Resource
    private CheckBarCodeImgStep checkBarCodeImgStep;

    @Resource
    private CheckBrandCategoryStep checkBrandCategoryStep;

    @Resource
    private CheckProductDetailStep checkProductDetailStep;

    @Resource
    private CheckSpecImgStep checkSpecImgStep;

    @Resource
    private CreateCSPUStep createCSPUStep;

    @Resource
    private CreateCSPULogStep createCSPULogStep;

    @Resource
    private SendCSPUCreatedEventStep sendCSPUCreatedEventStep;


    public Long 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 設計相似,這種設計有點繞,不夠直觀,以下所示:

public class MetricsServiceImpl implements MetricsServiceI{

    @Autowired
    private CommandBusI commandBus;

    @Override
    public Response addATAMetric(ATAMetricAddCmd cmd) {
        return commandBus.send(cmd);
    }

    @Override
    public Response addSharingMetric(SharingMetricAddCmd cmd) {
        return commandBus.send(cmd);
    }

    @Override
    public Response addPatentMetric(PatentMetricAddCmd cmd) {
        return  commandBus.send(cmd);
    }

    @Override
    public Response addPaperMetric(PaperMetricAddCmd cmd) {
        return  commandBus.send(cmd);
    }
}

看起來還挺乾淨的,但是 ATAMetricAddCmd 究竟是被哪一個 Executor 處理的呢,不直觀。我還要去理解 CommandBus,以及 CommandBus 是如何註冊 Executor 的。無形中增長了認知成本,很差。

既然這樣,爲什麼不用奧卡姆剃刀把這個 CommandBus 剔除呢。以下所示,去除 CommandBus 以後,代碼是否是直觀了不少,惟一的損失是咱們會失去框架層面提供的 Interceptor 功能,然而,Interceptor 正是我下一個要動刀的地方。

public class MetricsServiceImpl implements MetricsServiceI{

    @Resource
    private ATAMetricAddCmdExe ataMetricAddCmdExe;
    @Resource
    private SharingMetricAddCmdExe sharingMetricAddCmdExe;
    @Resource
    private PatentMetricAddCmdExe patentMetricAddCmdExe;
    @Resource
    private PaperMetricAddCmdExe paperMetricAddCmdExe;

    @Override
    public Response addATAMetric(ATAMetricAddCmd cmd) {
        return ataMetricAddCmdExe.execute(cmd);
    }

    @Override
    public Response addSharingMetric(SharingMetricAddCmd cmd) {
        return sharingMetricAddCmdExe.execute(cmd);
    }

    @Override
    public Response addPatentMetric(PatentMetricAddCmd cmd) {
        return  patentMetricAddCmdExe.execute(cmd);
    }

    @Override
    public 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 等架構思想的一種應用架構。

image.png

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

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

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

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

COLA 開源地址:

https://github.com/alibaba/COLA

相關文章
相關標籤/搜索