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