Go 項(xiàng)目怎么做好分層架構(gòu)和目錄規(guī)劃
開(kāi)發(fā)項(xiàng)目的時(shí)候我們都愛(ài)說(shuō)XX模塊,模塊一般是跟著項(xiàng)目所服務(wù)的業(yè)務(wù)走的。而項(xiàng)目的分層則沒(méi)有那么依賴具體的業(yè)務(wù)類(lèi)型,靠一些軟件設(shè)計(jì)的方法論和經(jīng)驗(yàn)在項(xiàng)目搭建初期就能大體確定其結(jié)構(gòu)。
我給大家介紹一下Go項(xiàng)目的分層架構(gòu)設(shè)計(jì),把整個(gè)項(xiàng)目的結(jié)構(gòu)按職能進(jìn)行劃分,規(guī)劃出整個(gè)項(xiàng)目的目錄結(jié)構(gòu)。
圖片
分層架構(gòu)
談到給項(xiàng)目的代碼分層,必然少不了對(duì)分層架構(gòu)的回顧。分層架構(gòu)如下圖所示
圖片
分層架構(gòu)的一個(gè)重要原則是:每層只能與位于其下方的層發(fā)生耦合。我們大多數(shù)時(shí)候使用的是松散型分層架構(gòu),允許上層與任意下層發(fā)生耦合。
這里說(shuō)的耦合可以先理解成包和包之間的引用關(guān)系,這樣更好理解一些。所以在我們?cè)O(shè)計(jì)項(xiàng)目的結(jié)構(gòu)時(shí),要注意下層的package 一定不能引用上層的package。使用松散型分層架構(gòu)的目的是讓我們的設(shè)計(jì)能更靈活,必要時(shí)出現(xiàn)跨層直接訪問(wèn)的情況也是被允許的。注意哦,不是推薦我們有事兒沒(méi)事都直接在用戶接口層訪問(wèn)DAO查數(shù)據(jù)哦。
舉個(gè)例子假如有個(gè)舊項(xiàng)目把很多東西都寫(xiě)在了controller里,又假如你是那個(gè)接過(guò)來(lái)要負(fù)責(zé)它的苦命人,你本來(lái)下定決心以后的新代碼都好好寫(xiě)不能再這么潦草下去啦,比如說(shuō)你把把一些新的邏輯放到service里。
但是業(yè)務(wù)系統(tǒng)一般都是在老需求基礎(chǔ)上迭代,新老代碼會(huì)有調(diào)用關(guān)系,這時(shí)候你卻發(fā)現(xiàn)原來(lái)的邏輯都在controller里,那這時(shí)你要不把用到的老邏輯往service放一份,要不你也徹底放棄往controller直接寫(xiě)完事兒啦,你咋選?
項(xiàng)目排期那么緊,我估計(jì)換誰(shuí)都是徹底放棄,就往controller里寫(xiě)吧。所以在項(xiàng)目搭建的開(kāi)始階段就確定后分層結(jié)構(gòu)還是很有必要的,后期做需求開(kāi)發(fā)時(shí)就可以相對(duì)無(wú)腦一些按照層次結(jié)構(gòu)往里面套,不同的邏輯寫(xiě)到不同的層里。
上面這個(gè)例子是不是很好的體現(xiàn)了大家平時(shí)在公司接管項(xiàng)目初期的心理呀,我相信多少人都遇到過(guò)這種情況。
好了,回到主題,下面簡(jiǎn)單說(shuō)一下分層架構(gòu)中各個(gè)層的職責(zé)。
用戶接口層:
用戶接口層只用于處理用戶界面顯示和用戶的請(qǐng)求響應(yīng),針對(duì)后端API服務(wù),基本上該層就是負(fù)責(zé)接受用戶請(qǐng)求、驗(yàn)證請(qǐng)求、調(diào)用下層拿到結(jié)果返回響應(yīng),在這里不應(yīng)該包含核心業(yè)務(wù)邏輯。
應(yīng)用層
應(yīng)用層里面是應(yīng)用服務(wù),主要負(fù)責(zé)用例流的任務(wù)協(xié)調(diào),每個(gè)用例流對(duì)應(yīng)一個(gè)服務(wù)方法(可以理解為API接口),應(yīng)用服務(wù)是領(lǐng)域服務(wù)的直接調(diào)用者,它主要協(xié)調(diào)對(duì)領(lǐng)域服務(wù)的操作,同時(shí)像發(fā)送基于某個(gè)事件的消息通知、發(fā)郵件、短信給用戶等操作都會(huì)寫(xiě)在應(yīng)用層,這樣能讓領(lǐng)域服務(wù)能專注于核心的業(yè)務(wù)邏輯。
應(yīng)用服務(wù)還有一個(gè)作用是,當(dāng)一個(gè)API的邏輯需要多個(gè)領(lǐng)域服務(wù)一起協(xié)作來(lái)完成時(shí),一個(gè)清晰的解決方案是通過(guò)應(yīng)用服務(wù)來(lái)對(duì)多個(gè)領(lǐng)域服務(wù)來(lái)進(jìn)行協(xié)調(diào)調(diào)用。
圖片
領(lǐng)域?qū)?/span>
領(lǐng)域?qū)邮钦嬲龑?xiě)業(yè)務(wù)邏輯的地方,這個(gè)業(yè)務(wù)邏輯可以理解成本領(lǐng)域的核心業(yè)務(wù)邏輯,比如怎么通過(guò)CRUD完成某件事寫(xiě)在這里,而成功或者失敗后向什么地方推送消息通知、調(diào)用其他領(lǐng)域服務(wù)、請(qǐng)求其他API 這些核心之外的業(yè)務(wù)邏輯則寫(xiě)在應(yīng)用層的應(yīng)用服務(wù)里,領(lǐng)域?qū)又魂P(guān)注本領(lǐng)域里的業(yè)務(wù)邏輯,應(yīng)用層負(fù)責(zé)協(xié)調(diào)調(diào)度它們。
基礎(chǔ)層
基礎(chǔ)層放置我們?yōu)轫?xiàng)目提供的一些公共、通用的能力:數(shù)據(jù)的訪問(wèn)和持久化、對(duì)接第三方平臺(tái)能力而封裝的庫(kù)、為項(xiàng)目開(kāi)發(fā)的基礎(chǔ)組件等都放在這一層。
注意這里說(shuō)的層都是概念性的,不是指具體項(xiàng)目中的某個(gè)目錄或者package。
分層后的目錄結(jié)構(gòu)
我們的Go項(xiàng)目,按照分層架構(gòu)進(jìn)行規(guī)劃后,可以用下面這張圖表示。
圖片
圖中的邏輯層我是用虛線框住的,代表所有與邏輯相關(guān)的應(yīng)該放在應(yīng)用和領(lǐng)域?qū)又?,它們邏輯?cè)重點(diǎn)有些不同,上面我們已經(jīng)說(shuō)過(guò)應(yīng)用和領(lǐng)域?qū)拥膮^(qū)別了,我們?cè)趯诮坛汤镞€有更多的實(shí)際需求的例子來(lái)體現(xiàn)它們之間的區(qū)別。
整個(gè)項(xiàng)目按分層架構(gòu)以及各種實(shí)際功能的需要,目錄結(jié)構(gòu)的規(guī)劃如下
.
|---api
| |---controller # 控制器
| |---reply # 響應(yīng)對(duì)象
| |---request # 請(qǐng)求對(duì)象
| |---router # 路由
|---common
| |---app # 分頁(yè)和接口響應(yīng)處理
| |---enum # 枚舉
| |---errcode # 項(xiàng)目錯(cuò)誤管理
| |---logger # 項(xiàng)目的日志門(mén)面
| |---middleware # 中間件
| |---util # 輔助函數(shù)
|---config # 配置
|---dal # 數(shù)據(jù)訪問(wèn)層
| |---cache # 緩存
| |---dao # 數(shù)據(jù)訪問(wèn)對(duì)象
| |---model # 數(shù)據(jù)模型對(duì)象
|---event
|---library
|---logic # 邏輯層
| |---appservice # 應(yīng)用服務(wù)
| |---domainservice # 領(lǐng)域服務(wù)
| |---do # 領(lǐng)域?qū)ο?
|---resources # 資源目錄
|---test # 測(cè)試腳本
怎么防止分層"塌陷”
代碼有了分層后,如果使用不當(dāng)一定會(huì)導(dǎo)致分層塌陷,最后還是把代碼寫(xiě)成一坨,那怎么能盡量減少這在情況出現(xiàn)呢?除了"各個(gè)層職責(zé)單一"的片湯話外其實(shí)是有明確的辦法的,老外把這個(gè)東西叫做防腐層。
防腐層有很多種,簡(jiǎn)單和最常用的就是各種數(shù)據(jù)對(duì)象, 他們之間的轉(zhuǎn)化讓各個(gè)層都能獨(dú)立的發(fā)展,能最大限度避免代碼層的塌陷。
項(xiàng)目中設(shè)計(jì)了四種數(shù)據(jù)對(duì)象:請(qǐng)求對(duì)象,數(shù)據(jù)實(shí)體Model對(duì)象,領(lǐng)域?qū)ο蠛晚憫?yīng)對(duì)象
下面這張圖展示了一個(gè)完整的API請(qǐng)求中客戶端與服務(wù)的完整交互過(guò)程中每種數(shù)據(jù)對(duì)象產(chǎn)生的時(shí)段和位置。根據(jù)API請(qǐng)求、邏輯的復(fù)雜程度我們可以有選擇的選擇其中幾個(gè)對(duì)象完成接口的請(qǐng)求和響應(yīng)數(shù)據(jù)的返回。
圖片
通過(guò)上面四種數(shù)據(jù)對(duì)象,程序的每個(gè)分層都可以專注自己的事兒,DAO層、Service層不必考慮接口要返回什么格式,用戶接口層也不用怕把一些不該暴露的字段數(shù)據(jù)給暴露了出去。