代碼分層,對(duì)于任何一個(gè)Java Web開(kāi)發(fā)來(lái)說(shuō)應(yīng)該都不陌生。一個(gè)好的層次劃分不僅可以能使代碼結(jié)構(gòu)更加清楚,還可以使項(xiàng)目分工更加明確,可讀性大大提升,更加有利于后期的維護(hù)和升級(jí)。
從另外一個(gè)角度來(lái)看,好的代碼分層架構(gòu),應(yīng)該是可以很好的匹配上單一職責(zé)原則的。這樣就可以降低層與層之間的依賴(lài),還能最大程度的復(fù)用各層的邏輯。本文就來(lái)介紹下Java Web項(xiàng)目的代碼到底應(yīng)該如何分層。
三層架構(gòu)
在軟件體系架構(gòu)設(shè)計(jì)中,分層式結(jié)構(gòu)是最常見(jiàn),也是最重要的一種結(jié)構(gòu)。微軟推薦的分層式結(jié)構(gòu)一般分為三層,從下至上分別為:數(shù)據(jù)訪問(wèn)層、業(yè)務(wù)邏輯層(又或稱(chēng)為領(lǐng)域?qū)樱⒈硎緦?。這也是Java Web中重要的三層架構(gòu)中的三個(gè)層次。區(qū)分層次的目的即為了“高內(nèi)聚低耦合”的思想。
所謂三層體系結(jié)構(gòu),是在客戶(hù)端與數(shù)據(jù)庫(kù)之間加入了一個(gè)“中間層”,也叫組件層。這里所說(shuō)的三層體系,不是指物理上的三層,不是簡(jiǎn)單地放置三臺(tái)機(jī)器就是三層體系結(jié)構(gòu),也不僅僅有B/S應(yīng)用才是三層體系結(jié)構(gòu),三層是指邏輯上的三層,即把這三個(gè)層放置到一臺(tái)機(jī)器上。
數(shù)據(jù)訪問(wèn)層
主要是對(duì)非原始數(shù)據(jù)(數(shù)據(jù)庫(kù)或者文本文件等存放數(shù)據(jù)的形式)的操作層,而不是指原始數(shù)據(jù),也就是說(shuō),是對(duì)數(shù)據(jù)庫(kù)的操作,而不是數(shù)據(jù),具體為業(yè)務(wù)邏輯層或表示層提供數(shù)據(jù)服務(wù)。
業(yè)務(wù)邏輯層
主要是針對(duì)具體的問(wèn)題的操作,也可以理解成對(duì)數(shù)據(jù)層的操作,對(duì)數(shù)據(jù)業(yè)務(wù)邏輯處理,如果說(shuō)數(shù)據(jù)層是積木,那邏輯層就是對(duì)這些積木的搭建。
界面層
主要表示W(wǎng)EB方式。如果邏輯層相當(dāng)強(qiáng)大和完善,無(wú)論表現(xiàn)層如何定義和更改,邏輯層都能完善地提供服務(wù)。
三層架構(gòu)與MVC的區(qū)別
MVC(模型Model-視圖View-控制器Controller)是一種架構(gòu)模式,可以用它來(lái)創(chuàng)建在域?qū)ο蠛蚒I表示層對(duì)象之間的區(qū)分。
同樣是架構(gòu)級(jí)別的,相同的地方在于他們都有一個(gè)表現(xiàn)層,但是他們不同的地方在于其他的兩個(gè)層。
在三層架構(gòu)中沒(méi)有定義Controller的概念。這是最不同的地方。而MVC也沒(méi)有把業(yè)務(wù)的邏輯訪問(wèn)看成兩個(gè)層,這是采用三層架構(gòu)或MVC搭建程序最主要的區(qū)別。
分層的最佳實(shí)踐
隨著網(wǎng)站的用戶(hù)量的不斷提升,系統(tǒng)架構(gòu)也在不斷的調(diào)整。有時(shí)候,隨著業(yè)務(wù)越來(lái)越復(fù)雜,有時(shí)候三層架構(gòu)好像不夠用了。比如,我們的應(yīng)用除了要給用戶(hù)提供頁(yè)面訪問(wèn)以外,還需要提供一些開(kāi)放接口,供外部系統(tǒng)調(diào)用。這個(gè)接口既不屬于界面層,也不應(yīng)該屬于業(yè)務(wù)邏輯層,因?yàn)樗€可能包含一些和業(yè)務(wù)邏輯無(wú)關(guān)的處理,如權(quán)限控制、流量控制等。
還有,隨著微服務(wù)的盛行,我們應(yīng)用中可能要依賴(lài)很多外部接口或第三方平臺(tái)。這部分代碼放下業(yè)務(wù)邏輯層和數(shù)據(jù)訪問(wèn)層也都不合適。
所以,漸漸的,在三層架構(gòu)的基礎(chǔ)上,系統(tǒng)架構(gòu)的分層變得更加復(fù)雜了。也正是因?yàn)閺?fù)雜,就非??简?yàn)架構(gòu)設(shè)計(jì)能力,因?yàn)閷哟蝿澐值牟缓?,很可能?huì)影響后面的開(kāi)發(fā),給代碼維護(hù)帶來(lái)很大的困難。
下圖,是阿里巴巴(參考《阿里巴巴Java開(kāi)發(fā)手冊(cè)》)提倡的應(yīng)用分層結(jié)構(gòu):
開(kāi)放接口層
可直接封裝 Service 方法暴露成 RPC 接口;通過(guò) Web 封裝成 http 接口;進(jìn)行網(wǎng)關(guān)安全控制、流量控制等。
終端顯示層
各個(gè)端的模板渲染并執(zhí)行顯示的層。當(dāng)前主要是 velocity 渲染,JS 渲染,JSP 渲染,移動(dòng)端展示等。
Web 層
主要是對(duì)訪問(wèn)控制進(jìn)行轉(zhuǎn)發(fā),各類(lèi)基本參數(shù)校驗(yàn),或者不復(fù)用的業(yè)務(wù)簡(jiǎn)單處理等。
Service 層
相對(duì)具體的業(yè)務(wù)邏輯服務(wù)層。
Manager 層
通用業(yè)務(wù)處理層,它有如下特征: 1) 對(duì)第三方平臺(tái)封裝的層,預(yù)處理返回結(jié)果及轉(zhuǎn)化異常信息; 2) 對(duì) Service 層通用能力的下沉,如緩存方案、中間件通用處理; 3) 與 DAO 層交互,對(duì)多個(gè) DAO 的組合復(fù)用。
DAO 層
數(shù)據(jù)訪問(wèn)層,與底層 MySQL、Oracle、Hbase 等進(jìn)行數(shù)據(jù)交互。
外部接口或第三方平臺(tái)
包括其它部門(mén) RPC 開(kāi)放接口,基礎(chǔ)平臺(tái),其它公司的 HTTP 接口。
事務(wù)處理
在了解了分層之后,我們?cè)賮?lái)看一下寫(xiě)Java Web代碼的時(shí)候,大家比較關(guān)心的一個(gè)問(wèn)題,那就是涉及到數(shù)據(jù)庫(kù)操作的時(shí)候,事務(wù)處理應(yīng)該在哪一層控制呢?
關(guān)于這個(gè)問(wèn)題,仁者見(jiàn)仁,智者見(jiàn)智。作者認(rèn)為,事務(wù)處理應(yīng)該放在Service層和Manager層。
DAO層不應(yīng)該有事務(wù),應(yīng)該只是很純的 CRUD 等比較通用的數(shù)據(jù)訪問(wèn)方法。一個(gè)DAO應(yīng)該只處理和自己相關(guān)的操作,不要有任何組合。組合的事情交給上層。
Service層和Manager層一般會(huì)組合多個(gè)DAO的CRUD操作,例如:在注冊(cè)一個(gè)用戶(hù)的時(shí)候需要往日志表里 INSERT 日志,那么就在 Service 層構(gòu)造事務(wù),在該事務(wù)中調(diào)用 Dao 層的 User.Insert () 與 Log.Insert ()。
異常處理
異常處理是Java中比較重要的一個(gè)話題,在《Effective Java》中有很多關(guān)于異常處理的最佳實(shí)踐,這里不詳細(xì)介紹了,本文主要簡(jiǎn)單說(shuō)一下在應(yīng)用代碼分層之后,各個(gè)層次之間的異常應(yīng)該如何處理,是自己捕獲,還是向上一層拋出。
首先,每一層都是可能發(fā)生異常的。由于每一層的職責(zé)都不通,處理方式也可能有差別。
DAO層
在 DAO 層,產(chǎn)生的異常類(lèi)型可能有很多,可能是SQL相關(guān)的異常,也可能是數(shù)據(jù)庫(kù)連接相關(guān)的異常。
這一層的處理方式可以簡(jiǎn)單一點(diǎn),直接try-catch(Exception),然后封裝成DAOException拋給上一層。這一層一般不需要打印日志,交給Service或者M(jìn)anager層來(lái)打印。
try{ CRUD }catch(Exception e){ throw new DAOException(e); }
Manager/Service
首先,對(duì)于DAO層拋上來(lái)的異常一定要捕獲的,并且記錄日志打印現(xiàn)場(chǎng)。
但是值得注意的是,如果是需要事務(wù)控制的方法,要注意捕獲到異常之后再向上拋一個(gè)新的異常,如 TransactionRolledbackException,否則事務(wù)無(wú)法回滾。
這兩層發(fā)生的異??梢愿鶕?jù)情況決定是繼續(xù)向上拋還是自己處理掉。如果是自己可以處理的異常,就捕獲,打日志,然后通過(guò)ErrorCode等方式返回給上一層。如果是自己無(wú)法處理或者不知道該如何處理的異常,就直接拋給上一層來(lái)處理。
Web
首先,可以明確的一點(diǎn):Web層不應(yīng)該再往外拋異常,因?yàn)檫@一層一旦拋異常,就可能會(huì)導(dǎo)致用戶(hù)跳轉(zhuǎn)到不友好的錯(cuò)誤頁(yè)面甚至看到錯(cuò)誤信息等。
如果意識(shí)到這個(gè)異常將導(dǎo)致頁(yè)面無(wú)法正常渲染,那么就應(yīng)該直接跳轉(zhuǎn)到友好錯(cuò)誤頁(yè)面,加上用戶(hù)容易理解的錯(cuò)誤提示信息。
開(kāi)放接口層
這一層和Web層一樣,不可以拋出異常。一般通過(guò)ErrorCode和ErrorMessage反饋給外部調(diào)用方。
這一層,要自己處理好所有的異常,定義好ErrorCode,并記錄好日志,便于日后排查問(wèn)題。
總結(jié)
本文主要介紹了Java Web項(xiàng)目中代碼分層的方案,通過(guò)分層之后可以使沒(méi)一層更加專(zhuān)注,解除耦合。并簡(jiǎn)單介紹了一下分層之后的事務(wù)處理和異常處理的邏輯。
評(píng)論