這個問題困擾了三歪幾天
最近在整合各種的系統(tǒng),在這個過程中遇到了各種的問題,三歪今天來分享一下關(guān)于「項目結(jié)構(gòu)」或者說「二方包」的事。
我們先不聊「二方包」,因為初學(xué)或者還沒工作的同學(xué)可能沒聽過這個詞。
初學(xué)的時候或者剛做項目的時候,我們的項目架構(gòu)是怎么樣的呢?我翻出了我在大學(xué)的時候?qū)懙男emo:

可以看到的是,我們的項目只有一個Module,里邊我們分各種的包:dao/service/controller/utils...等等。
這看起來好像沒啥問題吧?
我們?nèi)サ焦纠镞叄赡芸吹降捻椖慷挤至硕鄠€Module,比如下圖:

有什么區(qū)別呢?我們用一個Module在里邊分各種的子包,看起來也還行。為什么我們要分多個Module呢?
我個人認為原因是這樣的:Maven本身就支持多模塊(Module)的管理,將不同的層分出來,項目看起來更加清晰,在改動的時候針對某個模塊去變更就好了。
- 比如,我把dao層分成一個模塊,當(dāng)我變更dao這個模塊的時候,我只需要關(guān)心這個模塊就好了,不需要關(guān)心service模塊或者web模塊
- 舉個例子:每層幾乎都會有自己的配置信息(配置文件),數(shù)據(jù)訪問層會有數(shù)據(jù)庫的配置文件、業(yè)務(wù)層也會有對應(yīng)的配置文件。我們抽出多個
Module(不同的Module放屬于自己的配置文件),從代碼結(jié)構(gòu)層面上會顯得更加清晰。
我們寫完的代碼是需要維護的,可維護性很重要。
很多編程方式客觀上沒有對錯之分,一致性很重要,可讀性很重要,團隊溝通效率很重要。程序員天生需要團隊協(xié)作,而協(xié)作的正能量要放在問題的有效溝通上。個性化應(yīng)盡量表現(xiàn)在系統(tǒng)架構(gòu)和算法效率的提升上,而不是在合作規(guī)范上進行糾纏不休的討論、爭論,最后沒有結(jié)論。
二方包
當(dāng)年我看《阿里巴巴開發(fā)手冊》的時候也寫過一篇總結(jié),當(dāng)時的文章也提到了那時候不咋懂二方包,現(xiàn)在我回來填坑了。
首先來科普一下什么是二方庫?(二方庫也叫二方包)
- 一方庫指的是本項目中的依賴
- 二方庫指的是公司內(nèi)部其他項目提供的依賴
- 三方庫指的是其他組織、公司等來自第三方的依賴
如果看過我之前文章的同學(xué)都知道,三歪在公司目前維護的是消息管理平臺,全公司發(fā)送的消息都會經(jīng)過我的系統(tǒng)。
我會打個「二方包」到公司的Maven倉庫,然后他們引入我的pom依賴,調(diào)用我的接口去下發(fā)消息。

假如我沒有分Module,所有的代碼都寫在同一個Module下,那我發(fā)到公司的Maven倉庫會發(fā)生什么?打包(deploy)這個過程是沒毛病的。
如果他們依賴了我這個沒有處理過的二方包,相當(dāng)于把我整個工程給依賴進去了,這是非常可怕的。

如果有從零搭過系統(tǒng)或者整合過系統(tǒng)的同學(xué)會知道,這個過程有會有多「版本」的坑。只要版本不一致,就會出現(xiàn)一大堆奇奇怪怪的問題,并且這些問題都不太好解決。
所以,一般我們的二方包都應(yīng)該是很清爽的。比如我提供的二方包,它就只有接口和接口所需要的實體,沒有雜余的繁瑣依賴。
業(yè)務(wù)方是不關(guān)心接口的實現(xiàn)的,我們只需要暴露接口就好了。
三歪一次經(jīng)歷
最近三歪在整合系統(tǒng)嘛,分享一次經(jīng)歷。
我負責(zé)的消息管理平臺系統(tǒng)的架構(gòu)是這樣的:

可以看到,我所負責(zé)的系統(tǒng)是分得很細的(很符合分布式的理念)。從功能上看,這些系統(tǒng)變成一個大工程也不是什么問題,只是這個系統(tǒng)會非常非常大。
消息管理平臺除了上面的系統(tǒng),還有其他的子系統(tǒng),比如說「ID映射」。這里當(dāng)初設(shè)計的時候也把它當(dāng)做一個系統(tǒng)給抽出去了。
「ID映射」應(yīng)該不難理解:業(yè)務(wù)方傳入的是站內(nèi)的userId,但要發(fā)的是短信。我這邊要把userId轉(zhuǎn)換成手機號,可以簡單理解這個系統(tǒng)就做的這么一個ID轉(zhuǎn)換的功能。
現(xiàn)在要規(guī)劃把這個「ID映射」給整合起來,原有的機器要下線了,要把這個服務(wù)的功能給整到消息管理平臺的系統(tǒng)中。(為啥要整到我這個架構(gòu)上?因為本身這個系統(tǒng)就只有我這邊在頻繁使用)
為啥要整合?現(xiàn)在的潮流可能是把單個系統(tǒng)拆出多個系統(tǒng)做”微服務(wù)“,但真正系統(tǒng)多起來未必是一件好事。
一些小的功能其實沒必要單獨分出來一個系統(tǒng),這是需要成本的,至少我們機器成本是有的(一個系統(tǒng)我們至少會有四臺機器)->兩臺線上,一臺預(yù)發(fā),一臺線下
OK,說完背景了以后,我們再來看看「ID映射」這個系統(tǒng)的代碼架構(gòu):

說白了,就是這個工程下有這三個Module,如果你要問我為什么沒看到dao層的Module,而是直接放到core 層,問就是當(dāng)初設(shè)計不合理。依我的理解,應(yīng)該至少是要把數(shù)據(jù)訪問層抽出一個Module。
可以看到的是,這個「ID映射」系統(tǒng)其實也是一個完整的系統(tǒng),從后臺管理頁面到數(shù)據(jù)庫都是完整的。
現(xiàn)在要把這個系統(tǒng)給整到消息管理平臺的架構(gòu)下,如果是你,你會怎么弄呢?
其實就兩種方式:
- 把這個「ID映射」系統(tǒng)整個搬到某一個系統(tǒng)中
- 把這個「ID映射」系統(tǒng)通過模塊拆出來,分到各個系統(tǒng)中。
最簡單的做法肯定是把這個系統(tǒng)的代碼搬到另一個系統(tǒng),然后就可以run起來了。但前人已經(jīng)把系統(tǒng)分得那么干凈了,如果我這樣干了,后面接手的人會不會錘我呢?
三歪認為服務(wù)相關(guān)的代碼,應(yīng)該就整合到專門提供服務(wù)的系統(tǒng)。后臺相關(guān)的代碼,就應(yīng)該整合到后臺相關(guān)的系統(tǒng)。即便我后臺系統(tǒng)發(fā)布了,絲毫不影響對外提供的服務(wù)。
所以,我決定把「ID映射」的各個Module抽出來,分到不同的系統(tǒng)中。把api層和部分core層的代碼的Module分到service系統(tǒng)中,把web層的Module分到admin系統(tǒng)中。

看似是挺完美的,我當(dāng)初也是這樣執(zhí)行的,于是我順利把api和core的代碼整合到service系統(tǒng)之后,正打算把web的代碼整合到admin系統(tǒng)中,遇到了一個問題。
web的代碼是controller層,它顯然會依賴service層的代碼,service層的代碼也顯然會依賴dao的代碼。
現(xiàn)在我已經(jīng)把api/core層的代碼大多數(shù)已經(jīng)遷到了service系統(tǒng),而admin系統(tǒng)是沒有這些代碼和依賴的,我需要整個系統(tǒng)是能跑通的,我能怎么辦?
此時能想到的有兩種方案:
把
service系統(tǒng)的代碼再到admin系統(tǒng)中實現(xiàn)。現(xiàn)在
service系統(tǒng)已經(jīng)實現(xiàn)好了,打個二方包,然后讓admin系統(tǒng)依賴。這里也有兩個方案:- 二方包不做任何處理,
admin系統(tǒng)直接依賴其所有的實現(xiàn)。 - 把
admin系統(tǒng)所依賴的接口再出一個api層,admin系統(tǒng)只需要依賴api層,實現(xiàn)遠程調(diào)用
- 二方包不做任何處理,
如果是你,你會選擇哪種?我們來分析一下:
- 第一種方案:
service系統(tǒng)的代碼再到admin系統(tǒng)實現(xiàn),這肯定會有代碼冗余的情況。畢竟service系統(tǒng)肯定會依賴dao層的,而admin系統(tǒng)最終也是需要依賴dao層。dao層沒有完全抽出,在前期就肯定會有代碼冗余的情況 - 第二種方案分支①:將
service系統(tǒng)已實現(xiàn)的代碼直接打成二方包,admin系統(tǒng)直接引入就沒有任何代碼冗余的問題。但這會引發(fā)其他問題:- 直接打成二方包意味著要把所有的實現(xiàn)依賴都打進去,
admin系統(tǒng)在引入的時候需要針對這個二方包做一系列的排包操作。(這個非常蛋疼) - 其實最致命的是我們干不了。我們的系統(tǒng)在發(fā)布的時候是分環(huán)境的(線上、預(yù)發(fā)、線下),我們會在發(fā)布的時候根據(jù)不同的環(huán)境使用不同的配置。如果我們此時直接打個二方包,我們是需要指定環(huán)境的,這說明我們只能用一個環(huán)境的配置,這是行不通的。
- 直接打成二方包意味著要把所有的實現(xiàn)依賴都打進去,
- 第二種方案分支②:把
admin系統(tǒng)所依賴的接口再出一個api層,實現(xiàn)遠程調(diào)用。從字面上,這是最優(yōu)雅的方案,但如果admin系統(tǒng)依賴的接口眾多的時候,實際踐行的時候會發(fā)現(xiàn)這工作量巨大。admin系統(tǒng)所依賴的接口有40+個,這些接口所依賴的實體有80+個。我搞了大半天,發(fā)現(xiàn)完全搞不動,工作量巨大!!這是單純的體力活
第二個方案的分支①三歪再來聊聊為什么說干不了,首先我們的實現(xiàn)是有配置的

我們在deploy的時候就必須指定一個環(huán)境,比如我們默認選擇線上環(huán)境的配置好了。打完包以后,這個包默認的環(huán)境就是給線上使用的。但是admin系統(tǒng)他還需要在線下環(huán)境啟動,怎么辦?沒辦法吧?
假設(shè)環(huán)境配置的問題能解決,等著我們還有各種依賴的問題。admin系統(tǒng)和二方包的依賴沖突了怎么辦?
比如說:相同的配置項,不同的配置信息。在service系統(tǒng)上能兼容(畢竟代碼都在同一個工程下),但直接打成二方包就沒法跟admin系統(tǒng)兼容了。
這只能刪除沖突的部分,然后再發(fā)包了吧?但沖突是admin系統(tǒng)沖突的,跟我service系統(tǒng)的有啥關(guān)系呢?這我要做成一個完美兼容admin和service系統(tǒng)的Module嗎?老實說,不太現(xiàn)實。
扯了一大堆,三歪最終選擇的是第一個方案。
在初期現(xiàn)在dao層的代碼肯定是在admin和service系統(tǒng)上冗余的。service層不好抽取,但dao層還是相對好抽取的啊。
為什么dao和service層的代碼不一樣?明明我在上面已經(jīng)講了怎么多直接抽取二方包的不可行性了。
dao層的代碼相較于service層的代碼要簡單多了,一個合格dao層應(yīng)該是只管訪問數(shù)據(jù)庫,不應(yīng)該有dao層的代碼去調(diào)service層的代碼的。
代碼的簡單意味著依賴會很少,比如我們的dao層就應(yīng)該只有Mybatis和Spring相關(guān)的依賴,其余的就不應(yīng)該有。而前面提到的環(huán)境配置的問題,我們可以交給業(yè)務(wù)方去實現(xiàn),不在dao層上做任何配置信息,開個hook給業(yè)務(wù)方去做。
像三歪這種”微服務(wù)“系統(tǒng),本應(yīng)該要把dao層給抽出來。再回看我的系統(tǒng)架構(gòu)圖,可以發(fā)現(xiàn)會有幾個系統(tǒng)都需要依賴dao,如果沒有抽出來,這必會冗余,冗余的代碼意味著不好維護。

規(guī)范
之前看不懂的阿里巴巴開發(fā)手冊,現(xiàn)在能看懂了。我建議有空的時候還是可以看看的,都說得挺有道理的。
阿里大佬們踩了一堆坑,然后總結(jié)出來的規(guī)范。
【強制】二方庫的新增或升級,保持除功能點之外的其它 jar 包仲裁結(jié)果不變。如果有改變,
必須明確評估和驗證,建議進行 dependency:resolve 前后信息比對,如果仲裁結(jié)果完全不一
致,那么通過 dependency:tree 命令,找出差異點,進行
排除 jar 包。
【參考】為避免應(yīng)用二方庫的依賴沖突問題,二方庫發(fā)布者應(yīng)當(dāng)遵循以下原則:
1)精簡可控原則。移除一切不必要的 API 和依賴,只包含 Service API、必要的領(lǐng)域模型對
象、Utils 類、常量、枚舉等。如果依賴其它二方庫,盡量是 provided 引入,讓二方庫使用
者去依賴具體版本號;無 log 具體實現(xiàn),只依賴日志框架。
2)穩(wěn)定可追溯原則。每個版本的變化應(yīng)該被記錄,二方庫由誰維護,源碼在哪里,都需要能
方便查到。除非用戶主動升級版本,否則公共二方庫的行為不應(yīng)該發(fā)生變化。
......
三歪瞎扯
為什么我們會用多模塊(Module)?其實就是讓我們的項目代碼變得更加清晰,像對外服務(wù)的api層就必須要抽出一個精簡的Module給別人使用。
不知道你們?nèi)绻芸赐赀@篇文章能不能有所啟發(fā),反正我已經(jīng)寫完了。如果你們感興趣的話,等我整合完這些系統(tǒng)我再來寫一篇關(guān)于這段時間的感受。
我是三歪,一個想要變強的男人,下期見。
各類知識點總結(jié)
下面的文章都有對應(yīng)的原創(chuàng)精美PDF,在持續(xù)更新中,可以來找我催更~
- 92頁的Mybatis
- 129頁的多線程
- 141頁的Servlet
- 158頁的JSP
- 76頁的集合
- 64頁的JDBC
- 105頁的數(shù)據(jù)結(jié)構(gòu)和算法
- 142頁的Spring
- 58頁的過濾器和監(jiān)聽器
- 30頁的HTTP
- 42頁的SpringMVC
- Hibernate
- AJAX
- Redis
- ......
掃碼或者微信搜Java3y?免費領(lǐng)取原創(chuàng)思維導(dǎo)圖、精美PDF。在公眾號回復(fù)「888」領(lǐng)取,PDF內(nèi)容純手打有任何不懂歡迎來問我。
原創(chuàng)電子書
原創(chuàng)思維導(dǎo)圖

![]() |
|


