1. 程式人生 > 其它 >如何保證同事的程式碼不會腐爛?一文帶你瞭解 阿里巴巴 COLA 架構

如何保證同事的程式碼不會腐爛?一文帶你瞭解 阿里巴巴 COLA 架構

一起養成寫作習慣!這是我參與「掘金日新計劃 · 4 月更文挑戰」的第1天,點選檢視活動詳情

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

我能想到的有這幾點:

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

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

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

  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/significantfrank/article/details/110934799

COLA 3.1.0 版本

https://blog.csdn.net/significantfrank/article/details/109529311

COLA 3.0.0 版本

https://blog.csdn.net/significantfrank/article/details/106976804

COLA 2.0.0 版本

https://blog.csdn.net/significantfrank/article/details/100074716

COLA 1.0.0 版本

https://blog.csdn.net/significantfrank/article/details/85785565