动态

详情 返回 返回

如何保證同事的代碼不會腐爛?一文帶你瞭解 Alibaba COLA 架構 - 动态 详情

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

我能想到的有這幾點:

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

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

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

  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層 mapper ibatis數據庫映射
Infra層 config 配置信息
Client SDK api 服務對外透出的API
Client SDK dto 服務對外的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層 mapper ibatis數據庫映射
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版本經過逐次精簡,發展到瞭如今的形態。在阿里雲代碼腳手架生成器中作為一個可選項,足見其已經趨於成熟。

下一篇文章,我會和大家一起討論下COLA組件庫中的一些重要組件,比如擴展點組件(cola-component-extension-starter),狀態機組件(cola-component-statemachine)等。這些組件並不強制和COLA綁定,你完全可以不使用這些組件,只使用COLA架構來設計你的應用。但是這些組件可以提升團隊的研發效率。

我們下期再見,我是在阿里搬磚的工程師 蠻三刀醬

持續的更新原創優質文章,離不開你的點贊,轉發和分享!

我的唯一技術公眾號:後端技術漫談

歡迎加我個人號:BrodyYang

參考

COLA Github

https://github.com/alibaba/COLA

COLA 4.0.0 版本

https://blog.csdn.net/signifi...

COLA 3.1.0 版本

https://blog.csdn.net/signifi...

COLA 3.0.0 版本

https://blog.csdn.net/signifi...

COLA 2.0.0 版本

https://blog.csdn.net/signifi...

COLA 1.0.0 版本

https://blog.csdn.net/signifi...

user avatar mannayang 头像 u_16297326 头像 xiaoniuhululu 头像 sofastack 头像 u_16502039 头像 debuginn 头像 xuxueli 头像 tech 头像 lenglingx 头像 u_11365552 头像 u_15702012 头像 lvlaotou 头像
点赞 40 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.