本文開始前,問大家一個問題,你覺得一份業務代碼,尤其是互聯網業務代碼,都有哪些特點?
我能想到的有這幾點:
每當我們新啟動一個代碼倉庫,都是信心滿滿,結構整潔。但是時間越往後,代碼就變得腐敗不堪,技術債務越來越龐大。
這種情況有解決方案嗎?也是有的:
而COLA,我們今天的主角,就是為了提供一個可落地的業務代碼結構規範,讓你的代碼腐爛的儘可能慢一些,讓團隊的開發效率儘可能快一些。
COLA是什麼COLA是由阿里大佬張建飛所提出的一種業務代碼架構的最佳實踐,並且已經在阿里雲腳手架代碼生成器中作為一個可選項,可見其已經擁有了一定影響力。

COLA 是 Clean Object-Oriented and Layered Architecture的縮寫,代表「整潔面向對象分層架構」。
在COLA 4.0,也就是目前最新的版本中,作者將COLA拆分為COLA架構(Archetype)和COLA組件(Components)兩個部分:
兩者互不干擾,可以獨立使用。
COLA整體架構首先主要談談COLA架構,COLA的官方博文中是這麼介紹的:
在平時我們的業務開發中,大部分的系統都需要:
正是有這樣的共性存在,才會有很多普適的架構思想出現,比如分層架構、六邊形架構、洋蔥圈架構、整潔架構(Clean Architecture)、DDD架構等等。
這些應用架構思想雖然很好,但我們很多同學還是「不講Co德,明白了很多道理,可還是過不好這一生」。問題就在於缺乏實踐和指導。COLA的意義就在於,他不僅是思想,還提供了可落地的實踐。應該是為數不多的應用架構層面的開源軟件。
COLA提供了一整套代碼架構,拿來即用。 其中包含了很多架構設計思想,包括討論度很高的領域驅動設計DDD等。
注意:每個人對於架構設計都有着自己的理解。所以對於COLA的架構,本篇文章也僅僅只是我自己對於COLA的粗淺理解,大家可以批判看待。
COLA分層架構先來看兩張官方介紹圖


其次,還有一個官方的表格,介紹了COLA中每個層的命名和含義:
這兩張圖和一個表格已經把整個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三端都要支持。

有了我們說的「controller」層,接下來有的小夥伴肯定就會想,是不是service層啦。
是,也不是。
傳統的Web應用中,完全可以只有一個service層給controller層調用,但是作為一個業務應用,除非你真的只是個前端頁面的無情吐數據機器,否則很大可能性你的應用會有很多其他上下游調用方,並且你需要提供接口給他們。
這時候你給他們的不應該是一個Web接口,應該是RPC調用的服務層接口,至於原因不是本文的重點,具體就不展開了。
所以在COLA中,你的adapter層,調用了client層,client層中就是你服務接口的定義。

從上圖中可以看到,client包里有:
注意,這裡只是服務接口定義,而不是服務層的具體實現,所以在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層。
domain層接下來便是domain,也就是領域層,先看一下領域層整體結構:

可以看到,首先是按照不同的領域(customer和order)分包,裡面則是三種主要的文件類型:
例如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也就是基礎設施層,這層有我們剛才提到的gatewayimpl網關實現,也有MyBatis的mapper等數據源的映射和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已經做的足夠清晰簡潔了,但是它仍然有不完美的地方,比如每個接口的出入參都會根據業務名做定義,導致了很多結構極為相似的DTO,DTO的爆炸增長是個問題。參考:ISSUE-271
但是總的來說,COLA只是給你提供了一種架構設計的思想,並不深入到強制你使用某種規範的層面,所以對於COLA中你覺得複雜,或者不理解的地方,很多時候需要你自己來做權衡,作取捨。取其精華,去其糟粕的運用到你的項目中。
總結COLA架構並不複雜,COLA已經從1.0版本經過逐次精簡,發展到了如今的形態。在阿里雲代碼腳手架生成器中作為一個可選項,足見其已經趨於成熟。
- EOF -
@Transactional註解加不加 rollbackFor = Exception.class 的區別?
看了我的MyBatis-Plus用法,同事也開始悄悄模仿了...
微軟新工具準確率達80%,程序員:真的栓 Q
看完本文有收穫?請轉發分享給更多人
關注「ImportNew」,提升Java技能
點讚和在看就是最大的支持❤️