本文開(kāi)始前慰枕,問(wèn)大家一個(gè)問(wèn)題诉濒,你覺(jué)得一份業(yè)務(wù)代碼醇份,尤其是互聯(lián)網(wǎng)業(yè)務(wù)代碼松蒜,都有哪些特點(diǎn)岳遥?
我能想到的有這幾點(diǎn):
- 互聯(lián)網(wǎng)業(yè)務(wù)迭代快丢氢,工期緊傅联,導(dǎo)致代碼結(jié)構(gòu)混亂,幾乎沒(méi)有代碼注釋和文檔疚察。
- 互聯(lián)網(wǎng)人員變動(dòng)頻繁蒸走,很容易接手別人的老項(xiàng)目,新人根本沒(méi)時(shí)間吃透代碼結(jié)構(gòu)貌嫡,緊迫的工期又只能讓屎山越堆越大比驻。
- 多人一起開(kāi)發(fā),每個(gè)人的編碼習(xí)慣不同岛抄,工具類代碼各用個(gè)的别惦,業(yè)務(wù)命名也經(jīng)常沖突,影響效率夫椭。
- 大部分團(tuán)隊(duì)幾乎沒(méi)有時(shí)間做代碼重構(gòu)掸掸,任由代碼腐爛。
每當(dāng)我們新啟動(dòng)一個(gè)代碼倉(cāng)庫(kù),都是信心滿滿扰付,結(jié)構(gòu)整潔堤撵。但是時(shí)間越往后,代碼就變得腐敗不堪悯周,技術(shù)債務(wù)越來(lái)越龐大粒督。
這種情況有解決方案嗎?也是有的:
- 小組內(nèi)定期做代碼重構(gòu)禽翼,解決技術(shù)債務(wù)屠橄。
- 組內(nèi)設(shè)計(jì)完善的應(yīng)用架構(gòu),讓代碼的腐爛來(lái)得慢一些闰挡。(當(dāng)然很難做到完全不腐爛)
- 設(shè)計(jì)盡量簡(jiǎn)單锐墙,讓不同層級(jí)的開(kāi)發(fā)都能快速看懂并上手開(kāi)發(fā),而不是在一堆復(fù)雜的沒(méi)人看懂的代碼上堆更多的屎山长酗。
而COLA溪北,我們今天的主角,就是為了提供一個(gè)可落地的業(yè)務(wù)代碼結(jié)構(gòu)規(guī)范夺脾,讓你的代碼腐爛的盡可能慢一些之拨,讓團(tuán)隊(duì)的開(kāi)發(fā)效率盡可能快一些。
COLA是什么
COLA是由阿里大佬張建飛所提出的一種業(yè)務(wù)代碼架構(gòu)的最佳實(shí)踐咧叭,并且已經(jīng)在阿里云腳手架代碼生成器中作為一個(gè)可選項(xiàng)蚀乔,可見(jiàn)其已經(jīng)擁有了一定影響力。
COLA 是 Clean Object-Oriented and Layered Architecture的縮寫(xiě)菲茬,代表“整潔面向?qū)ο蠓謱蛹軜?gòu)”吉挣。
在COLA 4.0,也就是目前最新的版本中婉弹,作者將COLA拆分為COLA架構(gòu)(Archetype)和COLA組件(Components)兩個(gè)部分:
- COLA架構(gòu):COLA應(yīng)用的代碼模板睬魂。
- COLA組件:提供一些非常有用的通用組件,這些組件可以幫助我們提升研發(fā)效率镀赌。
兩者互不干擾氯哮,可以獨(dú)立使用。
COLA整體架構(gòu)
首先主要談?wù)凜OLA架構(gòu)商佛,COLA的官方博文中是這么介紹的:
在平時(shí)我們的業(yè)務(wù)開(kāi)發(fā)中蛙粘,大部分的系統(tǒng)都需要:
- 接收request,響應(yīng)response威彰;
- 做業(yè)務(wù)邏輯處理,像校驗(yàn)參數(shù)穴肘,狀態(tài)流轉(zhuǎn)歇盼,業(yè)務(wù)計(jì)算等等;
- 和外部系統(tǒng)有聯(lián)動(dòng)评抚,像數(shù)據(jù)庫(kù)豹缀,微服務(wù)伯复,搜索引擎等;
正是有這樣的共性存在邢笙,才會(huì)有很多普適的架構(gòu)思想出現(xiàn)啸如,比如分層架構(gòu)、六邊形架構(gòu)氮惯、洋蔥圈架構(gòu)叮雳、整潔架構(gòu)(Clean Architecture)、DDD架構(gòu)等等妇汗。
這些應(yīng)用架構(gòu)思想雖然很好帘不,但我們很多同學(xué)還是“不講Co德,明白了很多道理杨箭,可還是過(guò)不好這一生”寞焙。問(wèn)題就在于缺乏實(shí)踐和指導(dǎo)。COLA的意義就在于互婿,他不僅是思想捣郊,還提供了可落地的實(shí)踐。應(yīng)該是為數(shù)不多的應(yīng)用架構(gòu)層面的開(kāi)源軟件慈参。
COLA提供了一整套代碼架構(gòu)呛牲,拿來(lái)即用。 其中包含了很多架構(gòu)設(shè)計(jì)思想懂牧,包括討論度很高的領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)DDD等侈净。
注意:每個(gè)人對(duì)于架構(gòu)設(shè)計(jì)都有著自己的理解。所以對(duì)于COLA的架構(gòu)僧凤,本篇文章也僅僅只是我自己對(duì)于COLA的粗淺理解畜侦,大家可以批判看待。
COLA分層架構(gòu)
先來(lái)看兩張官方介紹圖
其次躯保,還有一個(gè)官方的表格旋膳,介紹了COLA中每個(gè)層的命名和含義:
層次 | 包名 | 功能 | 必選 |
---|---|---|---|
Adapter層 | web | 處理頁(yè)面請(qǐng)求的Controller | 否 |
Adapter層 | wireless | 處理無(wú)線端的適配 | 否 |
Adapter層 | wap | 處理wap端的適配 | 否 |
App層 | executor | 處理request,包括command和query | 是 |
App層 | consumer | 處理外部message | 否 |
App層 | scheduler | 處理定時(shí)任務(wù) | 否 |
Domain層 | model | 領(lǐng)域模型 | 否 |
Domain層 | ability | 領(lǐng)域能力途事,包括DomainService | 否 |
Domain層 | gateway | 領(lǐng)域網(wǎng)關(guān)验懊,解耦利器 | 是 |
Infra層 | gatewayimpl | 網(wǎng)關(guān)實(shí)現(xiàn) | 是 |
Infra層 | mapper | ibatis數(shù)據(jù)庫(kù)映射 | 否 |
Infra層 | config | 配置信息 | 否 |
Client SDK | api | 服務(wù)對(duì)外透出的API | 是 |
Client SDK | dto | 服務(wù)對(duì)外的DTO | 是 |
這兩張圖和一個(gè)表格已經(jīng)把整個(gè)COLA架構(gòu)的絕大部分內(nèi)容展現(xiàn)給了大家,但是一下子這么多信息量可能很難消化尸变。
既然整個(gè)示例架構(gòu)項(xiàng)目是一個(gè)Maven父子結(jié)構(gòu)义图,那我們就從父模塊一個(gè)個(gè)好好過(guò)一遍。
首先父模塊的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層
該模塊作為整個(gè)應(yīng)用的啟動(dòng)模塊(通常是一個(gè)SpringBoot應(yīng)用)召烂,只承擔(dān)啟動(dòng)項(xiàng)目和全局相關(guān)配置項(xiàng)的存放職責(zé)碱工。代碼目錄如下:
將啟動(dòng)獨(dú)立出來(lái),好處是清晰簡(jiǎn)潔,也能讓新人一眼就看出如何運(yùn)行項(xiàng)目怕篷,以及項(xiàng)目的一些基礎(chǔ)依賴历筝。
adapter層
接下來(lái)我們按照之前架構(gòu)圖從上到下的順序,一個(gè)個(gè)看廊谓。
首先是demo-web-adapter模塊梳猪,這名字是不是很新鮮?但其實(shí)蒸痹,可以理解為平時(shí)我們用的controller層(對(duì)于Web應(yīng)用來(lái)說(shuō))春弥,換湯不換藥。
在COLA官方博客中电抚,也能找到如下的描述:
Controller這個(gè)名字主要是來(lái)自于MVC惕稻,因?yàn)槭荕VC,所以自帶了Web應(yīng)用的烙印蝙叛。然而俺祠,隨著mobile的興起,現(xiàn)在很少有應(yīng)用僅僅只支持Web端借帘,通常的標(biāo)配是Web蜘渣,Mobile,WAP三端都要支持肺然。
cilent層
有了我們說(shuō)的“controller”層蔫缸,接下來(lái)有的小伙伴肯定就會(huì)想,是不是service層啦际起。
是拾碌,也不是。
傳統(tǒng)的Web應(yīng)用中街望,完全可以只有一個(gè)service層給controller層調(diào)用校翔,但是作為一個(gè)業(yè)務(wù)應(yīng)用,除非你真的只是個(gè)前端頁(yè)面的無(wú)情吐數(shù)據(jù)機(jī)器灾前,否則很大可能性你的應(yīng)用會(huì)有很多其他上下游調(diào)用方防症,并且你需要提供接口給他們。
這時(shí)候你給他們的不應(yīng)該是一個(gè)Web接口哎甲,應(yīng)該是RPC調(diào)用的服務(wù)層接口蔫敲,至于原因不是本文的重點(diǎn),具體就不展開(kāi)了炭玫。
所以在COLA中奈嘿,你的adapter層,調(diào)用了client層吞加,client層中就是你服務(wù)接口的定義裙犹。
從上圖中可以看到酝惧,client包里有:
- api文件夾:存放服務(wù)接口定義
- dto文件夾:存放傳輸實(shí)體
注意,這里只是服務(wù)接口定義伯诬,而不是服務(wù)層的具體實(shí)現(xiàn),所以在adapter層中巫财,調(diào)用的其實(shí)是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);
}
}
而最終接口的具體實(shí)現(xiàn)邏輯放到了app層盗似。
@Service
@CatchAndLog
public class CustomerServiceImpl implements CustomerServiceI {
@Resource
private CustomerListByNameQryExe customerListByNameQryExe;
@Override
public MultiResponse<CustomerDTO> listByName(CustomerListByNameQry customerListByNameQry) {
return customerListByNameQryExe.execute(customerListByNameQry);
}
}
app層
接著上面說(shuō)的,我們的app模塊作為服務(wù)的實(shí)現(xiàn)平项,存放了各個(gè)業(yè)務(wù)的實(shí)現(xiàn)類赫舒,并且嚴(yán)格按照業(yè)務(wù)分包,這里劃重點(diǎn)闽瓢,是先按照業(yè)務(wù)分包接癌,再按照功能分包的,為何要這么做扣讼,文章后面還會(huì)多說(shuō)兩句缺猛,先看圖:
customer和order分別對(duì)應(yīng)了消費(fèi)著和訂單兩個(gè)業(yè)務(wù)子領(lǐng)域。里面是COLA定義app層下面三種功能:
App層 | executor | 處理request椭符,包括command和query | 是 |
---|---|---|---|
App層 | consumer | 處理外部message | 否 |
App層 | scheduler | 處理定時(shí)任務(wù) | 否 |
可以看到荔燎,消息隊(duì)列的消費(fèi)者和定時(shí)任務(wù),這類平時(shí)我們業(yè)務(wù)開(kāi)發(fā)經(jīng)常會(huì)遇到的場(chǎng)景销钝,也放在app層有咨。
domain層
接下來(lái)便是domain,也就是領(lǐng)域?qū)诱艚。瓤匆幌骂I(lǐng)域?qū)诱w結(jié)構(gòu):
可以看到座享,首先是按照不同的領(lǐng)域(customer和order)分包,里面則是三種主要的文件類型:
- 領(lǐng)域?qū)嶓w:實(shí)體模型可以是充血模型(請(qǐng)自行了解)似忧,例如官方示例里的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; //注冊(cè)資金大于1000萬(wàn)的是大企業(yè)
}
public boolean isSME() {
return registeredCapital > 10000 && registeredCapital < 1000000; //注冊(cè)資金大于10萬(wàn)小于100萬(wàn)的為中小企業(yè)
}
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");
}
}
}
領(lǐng)域能力:domainservice文件夾下渣叛,是領(lǐng)域?qū)ν獗┞兜姆?wù)能力,如上圖中的CreditChecker
領(lǐng)域網(wǎng)關(guān):gateway文件夾下的接口定義橡娄,這里的接口你可以粗略的理解成一種SPI诗箍,也就是交給infrastructure層去實(shí)現(xiàn)的接口。
例如CustomerGateway里定義了接口getByById挽唉,要求infrastructure的實(shí)現(xiàn)類必須定義如何通過(guò)消費(fèi)者Id獲取消費(fèi)者實(shí)體信息滤祖,而infrastructure層可以實(shí)現(xiàn)任何數(shù)據(jù)源邏輯,比如瓶籽,從MySQL獲取匠童,從Redis獲取,還是從外部API獲取等等塑顺。
public interface CustomerGateway {
public Customer getByById(String customerId);
}
在示例代碼的CustomerGatewayImpl(位于infrastructure層)中汤求,CustomerDO(數(shù)據(jù)庫(kù)實(shí)體)經(jīng)過(guò)MyBatis的查詢俏险,轉(zhuǎn)換為了Customer領(lǐng)域?qū)嶓w,進(jìn)行返回扬绪。完成了依賴倒置竖独。
@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也就是基礎(chǔ)設(shè)施層,這層有我們剛才提到的gatewayimpl網(wǎng)關(guān)實(shí)現(xiàn)挤牛,也有MyBatis的mapper等數(shù)據(jù)源的映射和config配置文件莹痢。
Infra層 | gatewayimpl | 網(wǎng)關(guān)實(shí)現(xiàn) | 是 |
---|---|---|---|
Infra層 | mapper | ibatis數(shù)據(jù)庫(kù)映射 | 否 |
Infra層 | config | 配置信息 | 否 |
所有層講完了,COLA4.0很簡(jiǎn)單明了墓赴,最后竞膳,在引用一段官方介紹博客原文來(lái)總結(jié)COLA的層級(jí):
1)適配層(Adapter Layer):負(fù)責(zé)對(duì)前端展示(web,wireless诫硕,wap)的路由和適配坦辟,對(duì)于傳統(tǒng)B/S系統(tǒng)而言,adapter就相當(dāng)于MVC中的controller章办;
2)應(yīng)用層(Application Layer):主要負(fù)責(zé)獲取輸入锉走,組裝上下文,參數(shù)校驗(yàn)纲菌,調(diào)用領(lǐng)域?qū)幼鰳I(yè)務(wù)處理挠日,如果需要的話,發(fā)送消息通知等翰舌。層次是開(kāi)放的嚣潜,應(yīng)用層也可以繞過(guò)領(lǐng)域?qū)樱苯釉L問(wèn)基礎(chǔ)實(shí)施層椅贱;
3)領(lǐng)域?qū)樱―omain Layer):主要是封裝了核心業(yè)務(wù)邏輯懂算,并通過(guò)領(lǐng)域服務(wù)(Domain Service)和領(lǐng)域?qū)ο螅―omain Entity)的方法對(duì)App層提供業(yè)務(wù)實(shí)體和業(yè)務(wù)邏輯計(jì)算。領(lǐng)域是應(yīng)用的核心庇麦,不依賴任何其他層次计技;
4)基礎(chǔ)實(shí)施層(Infrastructure Layer):主要負(fù)責(zé)技術(shù)細(xì)節(jié)問(wèn)題的處理,比如數(shù)據(jù)庫(kù)的CRUD山橄、搜索引擎垮媒、文件系統(tǒng)、分布式服務(wù)的RPC等航棱。此外睡雇,領(lǐng)域防腐的重任也落在這里,外部依賴需要通過(guò)gateway的轉(zhuǎn)義處理饮醇,才能被上面的App層和Domain層使用它抱。
COLA架構(gòu)的特色
說(shuō)完了分層架構(gòu),我們?cè)賮?lái)回顧下上面提到的COLA架構(gòu)的幾個(gè)特色的設(shè)計(jì)
領(lǐng)域與功能的分包策略
也就是下面這張圖的意思朴艰,先按照領(lǐng)域分包观蓄,再按照功能分包混移,這樣做的其中一點(diǎn)好處是能將腐爛控制在該業(yè)務(wù)域內(nèi)。
比如消費(fèi)者customer和訂單order兩個(gè)領(lǐng)域是兩個(gè)后端開(kāi)發(fā)并行開(kāi)發(fā)侮穿,兩個(gè)人對(duì)于dto歌径,util這些文件夾的命名習(xí)慣都不同,那么只會(huì)腐爛在各自的業(yè)務(wù)包下面亲茅,而不會(huì)將dto,util,config等文件夾放在一起沮脖,極容易引發(fā)文件沖突。
前面的包定義芯急,都是功能維度的定義。為了兼顧領(lǐng)域維度的內(nèi)聚性驶俊,我們有必要對(duì)包結(jié)構(gòu)進(jìn)行一下微調(diào)娶耍,即頂層包結(jié)構(gòu)應(yīng)該是按照領(lǐng)域劃分,讓領(lǐng)域內(nèi)聚饼酿。
業(yè)務(wù)域和外部依賴解耦
前面提到的domain和infrastructure層的依賴倒置榕酒,是一個(gè)非常有用的設(shè)計(jì),進(jìn)一步解耦了取數(shù)邏輯的實(shí)現(xiàn)故俐。
例如下圖中想鹰,你的領(lǐng)域?qū)嶓w是商品item,通過(guò)gateway接口药版,你的商品的數(shù)據(jù)源可以是數(shù)據(jù)庫(kù)辑舷,也可以是外部的服務(wù)API。
如果是外部的商品服務(wù)槽片,你經(jīng)過(guò)API調(diào)用后何缓,商品域吐出的是一個(gè)大而全的DTO(可能包含幾十個(gè)字段),而在下單這個(gè)階段还栓,訂單所需要的可能只是其中幾個(gè)字段而已碌廓。你拿到了外部領(lǐng)域DTO,轉(zhuǎn)為自己領(lǐng)域的Item剩盒,只留下標(biāo)題價(jià)格庫(kù)存等必要的數(shù)據(jù)字段谷婆。
COLA并不完美
誠(chéng)然,COLA已經(jīng)做的足夠清晰簡(jiǎn)潔了辽聊,但是它仍然有不完美的地方纪挎,比如每個(gè)接口的出入?yún)⒍紩?huì)根據(jù)業(yè)務(wù)名做定義,導(dǎo)致了很多結(jié)構(gòu)極為相似的DTO身隐,DTO的爆炸增長(zhǎng)是個(gè)問(wèn)題廷区。參考:ISSUE-271
但是總的來(lái)說(shuō),COLA只是給你提供了一種架構(gòu)設(shè)計(jì)的思想贾铝,并不深入到強(qiáng)制你使用某種規(guī)范的層面隙轻,所以對(duì)于COLA中你覺(jué)得復(fù)雜埠帕,或者不理解的地方,很多時(shí)候需要你自己來(lái)做權(quán)衡玖绿,作取舍敛瓷。取其精華,去其糟粕的運(yùn)用到你的項(xiàng)目中斑匪。
總結(jié)
COLA架構(gòu)并不復(fù)雜呐籽,COLA已經(jīng)從1.0版本經(jīng)過(guò)逐次精簡(jiǎn),發(fā)展到了如今的形態(tài)蚀瘸。在阿里云代碼腳手架生成器中作為一個(gè)可選項(xiàng)狡蝶,足見(jiàn)其已經(jīng)趨于成熟。