自從用了 Alibaba COLA 架構,程式碼再也不怕腐爛了!

碼猿技術專欄發表於2023-05-09

大家好,我是不才陳某~

本文開始前,問大家一個問題,你覺得一份業務程式碼,尤其是網際網路業務程式碼,都有哪些特點?

我能想到的有這幾點:

  • 網際網路業務迭代快,工期緊,導致程式碼結構混亂,幾乎沒有程式碼註釋和文件
  • 網際網路人員變動頻繁,很容易接手別人的老專案,新人根本沒時間吃透程式碼結構,緊迫的工期又只能讓屎山越堆越大。
  • 多人一起開發,每個人的編碼習慣不同,工具類程式碼各用個的,業務命名也經常衝突,影響效率。
  • 大部分團隊幾乎沒有時間做程式碼重構,任由程式碼腐爛。

每當我們新啟動一個程式碼倉庫,都是信心滿滿,結構整潔。但是時間越往後,程式碼就變得腐敗不堪,技術債務越來越龐大。

這種情況有解決方案嗎?也是有的:

  1. 小組內定期做程式碼重構,解決技術債務。
  2. 組內設計完善的應用架構,讓程式碼的腐爛來得慢一些。(當然很難做到完全不腐爛)
  3. 設計儘量簡單,讓不同層級的開發都能快速看懂並上手開發,而不是在一堆複雜的沒人看懂的程式碼上堆更多的屎山。

而COLA,我們今天的主角,就是為了提供一個可落地的業務程式碼結構規範,讓你的程式碼腐爛的儘可能慢一些,讓團隊的開發效率儘可能快一些。

COLA是什麼

COLA是由阿里大佬張建飛所提出的一種業務程式碼架構的最佳實踐,並且已經在阿里雲腳手架程式碼生成器中作為一個可選項,可見其已經擁有了一定影響力。

COLA 是 Clean Object-Oriented and Layered Architecture的縮寫,代表“整潔物件導向分層架構”。

在COLA 4.0,也就是目前最新的版本中,作者將COLA拆分為COLA架構(Archetype)和COLA元件(Components)兩個部分:

  • COLA架構:COLA應用的程式碼模板。
  • COLA元件:提供一些非常有用的通用元件,這些元件可以幫助我們提升研發效率。

兩者互不干擾,可以獨立使用。

COLA整體架構

首先主要談談COLA架構,COLA的官方博文中是這麼介紹的:

在平時我們的業務開發中,大部分的系統都需要:

  • 接收request,響應response;
  • 做業務邏輯處理,像校驗引數,狀態流轉,業務計算等等;
  • 和外部系統有聯動,像資料庫,微服務,搜尋引擎等;

正是有這樣的共性存在,才會有很多普適的架構思想出現,比如分層架構、六邊形架構、洋蔥圈架構、整潔架構(Clean Architecture)、DDD架構等等。

這些應用架構思想雖然很好,但我們很多同學還是“不講Co德,明白了很多道理,可還是過不好這一生”。問題就在於缺乏實踐和指導。COLA的意義就在於,他不僅是思想,還提供了可落地的實踐。應該是為數不多的應用架構層面的開源軟體。

COLA提供了一整套程式碼架構,拿來即用。 其中包含了很多架構設計思想,包括討論度很高的領域驅動設計DDD等。

注意:每個人對於架構設計都有著自己的理解。所以對於COLA的架構,本篇文章也僅僅只是我自己對於COLA的粗淺理解,大家可以批判看待。

COLA分層架構

先來看兩張官方介紹圖

其次,還有一個官方的表格,介紹了COLA中每個層的命名和含義:

層次包名功能必選
Adapter層web處理頁面請求的Controller
Adapter層wireless處理無線端的適配
Adapter層wap處理wap端的適配
App層executor處理request,包括command和query
App層consumer處理外部message
App層scheduler處理定時任務
Domain層model領域模型
Domain層ability領域能力,包括DomainService
Domain層gateway領域閘道器,解耦利器
Infra層gatewayimpl閘道器實現
Infra層mapperibatis資料庫對映
Infra層config配置資訊
Client SDKapi服務對外透出的API
Client SDKdto服務對外的DTO

這兩張圖和一個表格已經把整個COLA架構的絕大部分內容展現給了大家,但是一下子這麼多資訊量可能很難消化。

既然整個示例架構專案是一個Maven父子結構,那我們就從父模組一個個好好過一遍。

首先父模組的pom.xml包含了如下子模組:

<modules>
  <module>demo-web-client</module>
  <module>demo-web-adapter</module>
  <module>demo-web-app</module>
  <module>demo-web-domain</module>
  <module>demo-web-infrastructure</module>
  <module>start</module>
</modules>

start層

該模組作為整個應用的啟動模組(通常是一個SpringBoot應用),只承擔啟動專案和全域性相關配置項的存放職責。程式碼目錄如下:

將啟動獨立出來,好處是清晰簡潔,也能讓新人一眼就看出如何執行專案,以及專案的一些基礎依賴。

adapter層

接下來我們按照之前架構圖從上到下的順序,一個個看。

首先是demo-web-adapter模組,這名字是不是很新鮮?但其實,可以理解為平時我們用的controller層(對於Web應用來說),換湯不換藥。

在COLA官方部落格中,也能找到如下的描述:

Controller這個名字主要是來自於MVC,因為是MVC,所以自帶了Web應用的烙印。然而,隨著mobile的興起,現在很少有應用僅僅只支援Web端,通常的標配是Web,Mobile,WAP三端都要支援。

cilent層

有了我們說的“controller”層,接下來有的小夥伴肯定就會想,是不是service層啦。

是,也不是。

傳統的Web應用中,完全可以只有一個service層給controller層呼叫,但是作為一個業務應用,除非你真的只是個前端頁面的無情吐資料機器,否則很大可能性你的應用會有很多其他上下游呼叫方,並且你需要提供介面給他們。

這時候你給他們的不應該是一個Web介面,應該是RPC呼叫的服務層介面,至於原因不是本文的重點,具體就不展開了。

所以在COLA中,你的adapter層,呼叫了client層,client層中就是你服務介面的定義。

從上圖中可以看到,client包裡有:

  • api資料夾:存放服務介面定義
  • dto資料夾:存放傳輸實體

注意,這裡只是服務介面定義,而不是服務層的具體實現,所以在adapter層中,呼叫的其實是client層的介面:

@RestController
public class CustomerController {

    @Autowired
    private CustomerServiceI customerService;

    @GetMapping(value = "/customer")
    public MultiResponse<CustomerDTO> listCustomerByName(@RequestParam(required = false) String name){
        CustomerListByNameQry customerListByNameQry = new CustomerListByNameQry();
        customerListByNameQry.setName(name);
        return customerService.listByName(customerListByNameQry);
    }

}

而最終介面的具體實現邏輯放到了app層。

@Service
@CatchAndLog
public class CustomerServiceImpl implements CustomerServiceI {

    @Resource
    private CustomerListByNameQryExe customerListByNameQryExe;

    @Override
    public MultiResponse<CustomerDTO> listByName(CustomerListByNameQry customerListByNameQry) {
        return customerListByNameQryExe.execute(customerListByNameQry);
    }
}

app層

接著上面說的,我們的app模組作為服務的實現,存放了各個業務的實現類,並且嚴格按照業務分包,這裡劃重點,是先按照業務分包,再按照功能分包的,為何要這麼做,文章後面還會多說兩句,先看圖:

customer和order分別對應了消費著和訂單兩個業務子領域。裡面是COLA定義app層下面三種功能:

App層executor處理request,包括command和query
App層consumer處理外部message
App層scheduler處理定時任務

可以看到,訊息佇列的消費者和定時任務,這類平時我們業務開發經常會遇到的場景,也放在app層。

domain層

接下來便是domain,也就是領域層,先看一下領域層整體結構:

可以看到,首先是按照不同的領域(customer和order)分包,裡面則是三種主要的檔案型別:

  1. 領域實體:實體模型可以是充血模型(請自行了解),例如官方示例裡的Customer.java如下:
@Data
@Entity
public class Customer{

    private String customerId;
    private String memberId;
    private String globalId;
    private long registeredCapital;
    private String companyName;
    private SourceType sourceType;
    private CompanyType companyType;

    public Customer() {
    }

    public boolean isBigCompany() {
        return registeredCapital > 10000000; //註冊資金大於1000萬的是大企業
    }

    public boolean isSME() {
        return registeredCapital > 10000 && registeredCapital < 1000000; //註冊資金大於10萬小於100萬的為中小企業
    }

    public void checkConfilict(){
        //Per different biz, the check policy could be different, if so, use ExtensionPoint
        if("ConflictCompanyName".equals(this.companyName)){
            throw new BizException(this.companyName+" has already existed, you can not add it");
        }

    }
}
  1. 領域能力:domainservice資料夾下,是領域對外暴露的服務能力,如上圖中的CreditChecker
  2. 領域閘道器:gateway資料夾下的介面定義,這裡的介面你可以粗略的理解成一種SPI,也就是交給infrastructure層去實現的介面。

例如CustomerGateway裡定義了介面getByById,要求infrastructure的實現類必須定義如何透過消費者Id獲取消費者實體資訊,而infrastructure層可以實現任何資料來源邏輯,比如,從MySQL獲取,從Redis獲取,還是從外部API獲取等等。

public interface CustomerGateway {
    public Customer getByById(String customerId);
}

在示例程式碼的CustomerGatewayImpl(位於infrastructure層)中,CustomerDO(資料庫實體)經過MyBatis的查詢,轉換為了Customer領域實體,進行返回。完成了依賴倒置。

@Component
public class CustomerGatewayImpl implements CustomerGateway {
    @Autowired
    private CustomerMapper customerMapper;

    public Customer getByById(String customerId){
      CustomerDO customerDO = customerMapper.getById(customerId);
      //Convert to Customer
      return null;
    }
}

infrastructure層

最後是我們的infrastructure也就是基礎設施層,這層有我們剛才提到的gatewayimpl閘道器實現,也有MyBatis的mapper等資料來源的對映和config配置檔案。

Infra層gatewayimpl閘道器實現
Infra層mapperibatis資料庫對映
Infra層config配置資訊

所有層講完了,COLA4.0很簡單明瞭,最後,在引用一段官方介紹部落格原文來總結COLA的層級:

1)適配層(Adapter Layer):負責對前端展示(web,wireless,wap)的路由和適配,對於傳統B/S系統而言,adapter就相當於MVC中的controller;

2)應用層(Application Layer):主要負責獲取輸入,組裝上下文,引數校驗,呼叫領域層做業務處理,如果需要的話,傳送訊息通知等。層次是開放的,應用層也可以繞過領域層,直接訪問基礎實施層;

3)領域層(Domain Layer):主要是封裝了核心業務邏輯,並透過領域服務(Domain Service)和領域物件(Domain Entity)的方法對App層提供業務實體和業務邏輯計算。領域是應用的核心,不依賴任何其他層次;

4)基礎實施層(Infrastructure Layer):主要負責技術細節問題的處理,比如資料庫的CRUD、搜尋引擎、檔案系統、分散式服務的RPC等。此外,領域防腐的重任也落在這裡,外部依賴需要透過gateway的轉義處理,才能被上面的App層和Domain層使用。

COLA架構的特色

說完了分層架構,我們再來回顧下上面提到的COLA架構的幾個特色的設計

領域與功能的分包策略

也就是下面這張圖的意思,先按照領域分包,再按照功能分包,這樣做的其中一點好處是能將腐爛控制在該業務域內。

比如消費者customer和訂單order兩個領域是兩個後端開發並行開發,兩個人對於dto,util這些資料夾的命名習慣都不同,那麼只會腐爛在各自的業務包下面,而不會將dto,util,config等資料夾放在一起,極容易引發檔案衝突。

前面的包定義,都是功能維度的定義。為了兼顧領域維度的內聚性,我們有必要對包結構進行一下微調,即頂層包結構應該是按照領域劃分,讓領域內聚。

業務域和外部依賴解耦

前面提到的domain和infrastructure層的依賴倒置,是一個非常有用的設計,進一步解耦了取數邏輯的實現。

例如下圖中,你的領域實體是商品item,透過gateway介面,你的商品的資料來源可以是資料庫,也可以是外部的服務API。

如果是外部的商品服務,你經過API呼叫後,商品域吐出的是一個大而全的DTO(可能包含幾十個欄位),而在下單這個階段,訂單所需要的可能只是其中幾個欄位而已。你拿到了外部領域DTO,轉為自己領域的Item,只留下標題價格庫存等必要的資料欄位。

COLA並不完美

誠然,COLA已經做的足夠清晰簡潔了,但是它仍然有不完美的地方,比如每個介面的出入參都會根據業務名做定義,導致了很多結構極為相似的DTO,DTO的爆炸增長是個問題。參考:ISSUE-271

但是總的來說,COLA只是給你提供了一種架構設計的思想,並不深入到強制你使用某種規範的層面,所以對於COLA中你覺得複雜,或者不理解的地方,很多時候需要你自己來做權衡,作取捨。取其精華,去其糟粕的運用到你的專案中。

總結

COLA架構並不複雜,COLA已經從1.0版本經過逐次精簡,發展到瞭如今的形態。在阿里雲程式碼腳手架生成器中作為一個可選項,足見其已經趨於成熟。

最後說一句(別白嫖,求關注)

陳某每一篇文章都是精心輸出,如果這篇文章對你有所幫助,或者有所啟發的話,幫忙點贊在看轉發收藏,你的支援就是我堅持下去的最大動力!

另外陳某的知識星球開通了,公眾號回覆關鍵詞:知識星球 獲取限量20元優惠券加入只需109元,星球回饋的價值巨大,目前更新了Spring全家桶實戰系列億級資料分庫分表實戰DDD微服務實戰專欄我要進大廠、Spring,Mybatis等框架原始碼、架構實戰22講、精盡RocketMQ等....每增加一個專欄價格將上漲20元

關注公眾號:【碼猿技術專欄】,公眾號內有超讚的粉絲福利,回覆:加群,可以加入技術討論群,和大家一起討論技術,吹牛逼!

相關文章