新項(xiàng)目模塊不能拆拆拆,但怎么應(yīng)對大型項(xiàng)目?
很多同學(xué)創(chuàng)建一個(gè)項(xiàng)目之后,就迫不及待的上手開寫了。項(xiàng)目代碼不像一些框架代碼一樣可以隨意的去寫,但一般都是采用MVC的模式進(jìn)行開發(fā)。很悲催的是,Java中Web開發(fā)的這些目錄名稱,到現(xiàn)在還是一團(tuán)亂麻,你需要自己去規(guī)劃。
什么Controller、Service、Dao等,但其實(shí)這種劃分方式弊端很多! 本文將先介紹兩種典型的分層結(jié)構(gòu),然后稍微借鑒一下DDD的思想,談一下我在項(xiàng)目中常用的目錄結(jié)構(gòu)。本篇文章非常的實(shí)用,將探討怎樣做一個(gè)應(yīng)對大型項(xiàng)目的目錄劃分。
清晰的目錄結(jié)構(gòu),能夠輔助其他同學(xué)輕而易舉的了解項(xiàng)目的功能模塊,在項(xiàng)目中保持整體一致的約定也是一個(gè)非常好的習(xí)慣。如果再加上一個(gè)擴(kuò)展性,那目錄劃分就是重中之重。
有兩種典型的分類方式,但也有很多細(xì)節(jié)。
1. 最簡單的MVC
我們平常最熟悉的,就是MVC結(jié)構(gòu)。這種結(jié)構(gòu)很流行,寫簡單項(xiàng)目很方便,但是會(huì)產(chǎn)生嚴(yán)重的耦合問題、Service爆炸問題,數(shù)千、上萬行的代碼是家常便飯。
- Model(模型)表示應(yīng)用程序核心(比如數(shù)據(jù)庫記錄字段)。
- View(視圖)顯示數(shù)據(jù)(數(shù)據(jù)庫記錄)。
- Controller(控制器)處理輸入(寫入數(shù)據(jù)庫記錄)。
在項(xiàng)目劃分上,就類似下面的目錄結(jié)構(gòu)。
1.1 模型
domain是DDD中一個(gè)非常寬泛的概念。不過,我們平常就當(dāng)作數(shù)據(jù)庫對應(yīng)的Java 類使用了(沒什么錯(cuò))。在實(shí)際操作中,它還可能有下面幾種名字,在普通項(xiàng)目中區(qū)別不大,你最好在項(xiàng)目中保持相同的意義來避免歧義。
- entity 這個(gè)意義比較明顯,就是實(shí)體的意思,最常用。比如JPA的Entity注解
- model模型的意思,一般用來在不同系統(tǒng)之間交互。但如果你的模型非常簡單,直接用entity來表示也是可以的
- domain 這個(gè)范圍有點(diǎn)大,甚至?xí)I(lǐng)域內(nèi)service。如果你對DDD的概念不是很熟悉,那就玩上面幾種
對于簡單的項(xiàng)目,我通常在項(xiàng)目中使用entity來表示和數(shù)據(jù)庫的交互。在JPA之類的ORM中,也是做相關(guān)處理的。比如javax.persistence.Entity注解。你要明白的是,Spring Data其實(shí)取了一個(gè)比較折衷的點(diǎn),把很多東西揉在一起了。
1.2 Dao
dao層叫數(shù)據(jù)訪問層,全稱為data access object,屬于一種比較底層,比較基礎(chǔ)的操作。在一些其他框架中,還會(huì)叫別的名字。
- mapper 這個(gè)一般是Mybaits之類的框架所生成的目錄,通常是一些接口。
- repository 倉庫的意思,在jpa中經(jīng)常用。
Dao應(yīng)該滿足最小封裝原則,理論上只涉及一句SQL的執(zhí)行。如果有多個(gè)數(shù)據(jù)的存取動(dòng)作,需要封裝在Service中,并用事務(wù)進(jìn)行管理(雖然這么說,但repository在DDD中,是不和具體的數(shù)據(jù)庫打交道的)。
1.3 service和controller
這個(gè)沒什么好說的,基本上所有重要的邏輯都在這里完成。service用于邏輯處理,controller用于接口暴露。
2. 根據(jù)功能組織
大多數(shù)情況下,我們使用上面的這種劃分模式,能夠很好的完成工作。比如,所有的數(shù)據(jù)處理,都放在Dao層,所有的邏輯處理,都放在Service層。
這在小項(xiàng)目中相安無事,但如果項(xiàng)目中,有成百上千個(gè)Entity,這些目錄中的文件就會(huì)爆炸,以至于最后無法維護(hù)。
另外一個(gè)問題就是,僅僅一個(gè)簡單的功能,就可能分散在多個(gè)package下的多個(gè)文件中,大型項(xiàng)目維護(hù)變得困難。
我們有另外一個(gè)思路,就是根據(jù)功能進(jìn)行分組。比如下面的截圖。
我們把相似功能,放在modules下的單個(gè)文件夾中。如果這個(gè)功能模塊比較大,我么可以在功能模塊下,再進(jìn)行分層設(shè)計(jì)。
比如上圖,有一個(gè)商品服務(wù),我們單獨(dú)給它分配了一個(gè)目錄空間goods,然后在里面又劃分了dao、entity等目錄;但對于Service和Controller,我們簡單的放在了外層,可以看到在模塊內(nèi)的分配是比較靈活的。
這么做的好處是顯而易見的。功能變的非常的集中,各個(gè)package之間的內(nèi)容互不影響。
3. 還是不夠優(yōu)雅
其實(shí),即使我們這樣劃分了,項(xiàng)目仍然會(huì)面臨很大的挑戰(zhàn)(很多DDD的書籍,會(huì)大量討論各層的交互)。
下面分享一個(gè)我在平常使用的分層模式,兼顧高內(nèi)聚和低耦合,有著良好的擴(kuò)展性。
- config,最外層的一些全局配置,比如web配置,消息隊(duì)列配置等
- system,全局的工具和依賴功能,在DDD中叫做基礎(chǔ)設(shè)施(但在非DDD實(shí)踐的項(xiàng)目中名稱太怪異了)
- auth,權(quán)限認(rèn)證模塊,比如JWT或者Spring Sercurity,這部分的設(shè)計(jì)要獨(dú)立,以便后續(xù)抽離到Zuul之類的網(wǎng)關(guān)
- bc,在DDD中是限界上下文的意思(Bounded Context),我們也可以直接叫模塊,這些模塊有著嚴(yán)格的界限,可以根據(jù)請求量,拆分成相應(yīng)的微服務(wù)。在上圖,crm、images、order等等,都可以抽離成獨(dú)立的微服務(wù)
我們再來看一下每個(gè)模塊之內(nèi)的結(jié)構(gòu)。
- 和傳統(tǒng)的MVC類似。不過,為了屏蔽變化,兼顧擴(kuò)展性,我們增加了更多的內(nèi)容。
- persitence,持久層,具體使用JPA還是Mybatis,這個(gè)是無關(guān)緊要的。我們的目標(biāo),就是盡量的弱化持久層的實(shí)現(xiàn),將變化封裝在Domain層中
- persitence/dao,具體的持久層接口,比如MyBatis的Mapper文件,或者JPA的Repository
- domain層,具體的業(yè)務(wù)層,你可以認(rèn)為是一堆Getter、Setter的Bean。我們盡量會(huì)把大多數(shù)驗(yàn)證類和變化封裝在這里(可以大體認(rèn)為是DDD中的充血模型)
- controller,具體的Rest接口層。但不同的是,有很多不同的請求和返回,我們封裝成了Request和Response,用來接受提交的數(shù)據(jù),對返回?cái)?shù)據(jù)進(jìn)行瘦身等
- application,應(yīng)對傳統(tǒng)的service層,除了在application能夠調(diào)用Dao,其他層是沒有權(quán)利調(diào)用Dao的
- api,和application的功能是相同的。只不過,api的接口,指的是模塊之間可以相互調(diào)用的接口。除了api暴露的這些接口,bc之間的類和接口,默認(rèn)彼此是不可見的
- util,不通用的util,會(huì)放在模塊內(nèi)部,而不是抽離出公共的util
除了要解決目錄方面的問題,我們還要把數(shù)據(jù)的流向給規(guī)劃清楚。
一個(gè)上層的應(yīng)用,是可以通過API接口直接調(diào)用下層服務(wù)的。比如,訂單系統(tǒng)訪問商品基礎(chǔ)信息的數(shù)據(jù);反之卻不可以,比如商品基礎(chǔ)信息模塊訪問訂單系統(tǒng)的接口。
低層想要對高層的數(shù)據(jù)產(chǎn)生變化,就只能通過消息模塊,將變更發(fā)布出去,其他的模塊就可以訂閱這些變化。
小結(jié)
綜上所述,xjjdog認(rèn)為,如果你的項(xiàng)目,可能會(huì)比較大,單純的使用分層的package,并不是一個(gè)好的習(xí)慣。
你可能對這種后臺(tái)管理類的項(xiàng)目駕輕就熟,有很多有用的模版,它們都是簡單的MVC分層。這應(yīng)付一些外包項(xiàng)目,干一些一錘子買賣的時(shí)活,或許沒什么問題,但一旦是比較大的長期項(xiàng)目,這種分層的目錄接口就顯現(xiàn)出它的弊端。
這是因?yàn)椋喉?xiàng)目的短期風(fēng)險(xiǎn),是工期問題;而長期風(fēng)險(xiǎn),是擴(kuò)展問題。隨著訪問量的增加,還有低耦合高內(nèi)聚的需求增加,如何快速的應(yīng)對需求,減少BUG,將會(huì)是制約項(xiàng)目發(fā)展的最主要因素。
作者簡介:小姐姐味道 (xjjdog),一個(gè)不允許程序員走彎路的公眾號。聚焦基礎(chǔ)架構(gòu)和Linux。十年架構(gòu),日百億流量,與你探討高并發(fā)世界,給你不一樣的味道。我的個(gè)人微信xjjdog0,歡迎添加好友,進(jìn)一步交流。