close

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

我能想到的有這幾點:

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

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

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

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

而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層的接口:

@RestControllerpublicclassCustomerController{@AutowiredprivateCustomerServiceIcustomerService;@GetMapping(value="/customer")publicMultiResponse<CustomerDTO>listCustomerByName(@RequestParam(required=false)Stringname){CustomerListByNameQrycustomerListByNameQry=newCustomerListByNameQry();customerListByNameQry.setName(name);returncustomerService.listByName(customerListByNameQry);}}

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

@Service@CatchAndLogpublicclassCustomerServiceImplimplementsCustomerServiceI{@ResourceprivateCustomerListByNameQryExecustomerListByNameQryExe;@OverridepublicMultiResponse<CustomerDTO>listByName(CustomerListByNameQrycustomerListByNameQry){returncustomerListByNameQryExe.execute(customerListByNameQry);}}app層

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

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

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

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

domain層

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

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

領域實體:實體模型可以是充血模型(請自行了解),例如官方示例里的Customer.java如下:
@Data@EntitypublicclassCustomer{privateStringcustomerId;privateStringmemberId;privateStringglobalId;privatelongregisteredCapital;privateStringcompanyName;privateSourceTypesourceType;privateCompanyTypecompanyType;publicCustomer(){}publicbooleanisBigCompany(){returnregisteredCapital>10000000;//註冊資金大於1000萬的是大企業}publicbooleanisSME(){returnregisteredCapital>10000&&registeredCapital<1000000;//註冊資金大於10萬小於100萬的為中小企業}publicvoidcheckConfilict(){//Perdifferentbiz,thecheckpolicycouldbedifferent,ifso,useExtensionPointif("ConflictCompanyName".equals(this.companyName)){thrownewBizException(this.companyName+"hasalreadyexisted,youcannotaddit");}}}
領域能力:domainservice文件夾下,是領域對外暴露的服務能力,如上圖中的CreditChecker
領域網關:gateway文件夾下的接口定義,這裡的接口你可以粗略的理解成一種SPI,也就是交給infrastructure層去實現的接口。

例如CustomerGateway里定義了接口getByById,要求infrastructure的實現類必須定義如何通過消費者Id獲取消費者實體信息,而infrastructure層可以實現任何數據源邏輯,比如,從MySQL獲取,從Redis獲取,還是從外部API獲取等等。

publicinterfaceCustomerGateway{publicCustomergetByById(StringcustomerId);}

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

@ComponentpublicclassCustomerGatewayImplimplementsCustomerGateway{@AutowiredprivateCustomerMappercustomerMapper;publicCustomergetByById(StringcustomerId){CustomerDOcustomerDO=customerMapper.getById(customerId);//ConverttoCustomerreturnnull;}}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版本經過逐次精簡,發展到了如今的形態。在阿里雲代碼腳手架生成器中作為一個可選項,足見其已經趨於成熟。

- EOF -

推薦閱讀點擊標題可跳轉

@Transactional註解加不加 rollbackFor = Exception.class 的區別?

看了我的MyBatis-Plus用法,同事也開始悄悄模仿了...

微軟新工具準確率達80%,程序員:真的栓 Q

看完本文有收穫?請轉發分享給更多人

關注「ImportNew」,提升Java技能

點讚和在看就是最大的支持❤️

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()