Go項(xiàng)目模塊劃分、邏輯分層解耦--代碼實(shí)戰(zhàn)
這節(jié)我以一個(gè)簡(jiǎn)單的創(chuàng)建訂單功能為例,把邏輯分層解藕的方法論用實(shí)際代碼再講解一遍。
圖片
演示按照可能是多數(shù)人的一個(gè)開發(fā)習(xí)慣:先定義好Model 、請(qǐng)求、響應(yīng)等數(shù)據(jù)對(duì)象,再按照自底向上的順序即--DAL->領(lǐng)域服務(wù)->應(yīng)用服務(wù)->控制器的順序進(jìn)行代碼編寫。
數(shù)據(jù)對(duì)象
model
先從Model開始,首先在dal/model 目錄下創(chuàng)建demo.go ,因?yàn)檫€沒有真正開發(fā)進(jìn)行需求的開發(fā),仍然算項(xiàng)目搭建過程中的測(cè)試代碼,所以我們把文件命名成了demo.go。
type DemoOrder struct {
Id int64 `gorm:"column:id;primary_key" json:"id"` //自增ID
UserId int64 `gorm:"column:user_id" json:"user_id"` //用戶ID
BillMoney int64 `gorm:"column:bill_money" json:"bill_money"` //訂單金額(分)
OrderNo string `gorm:"column:order_no;type:varchar(32)" json:"order_no"` //訂單號(hào)
State int8 `gorm:"column:state;default:1" json:"state"` //1-待支付,2-支付成功,3-支付失敗
PaidAt time.Time `gorm:"column:paid_at;default:\"1970-01-01 00:00:00\"" json:"paid_at"`
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` //創(chuàng)建時(shí)間
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` //更新時(shí)間
}
這里要說一下 IsDel 這個(gè)字段,這個(gè)字段被設(shè)置成了soft_delete.DeletedAt 類型。這個(gè)是GORM V2 中新增的特性讓軟刪除字段支持更多類型,在V1中軟刪除字段必須命名成deleted_at 并且字段在數(shù)據(jù)庫(kù)中的默認(rèn)值是NULL。
這在很多公司里DBA設(shè)置的約束里是不允許的,所以我之前沒有使用過。但是現(xiàn)在GORM V2 支持Flag 模式了,就是咱們很多人用的0代表未刪除 1代表刪除,那么這個(gè)特性就可以應(yīng)用起來了。
使用前需要先安裝GORM的soft_delete這個(gè)包。
go get -u "gorm.io/plugin/soft_delete"
在定義模型時(shí)給字段設(shè)置其類型和Tag標(biāo)簽
type DemoOrder struct {
...
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
...
}
那么這樣GORM在執(zhí)行SQL語句時(shí)就會(huì)自動(dòng)帶上is_del這個(gè)字段進(jìn)行查詢啦
// Query
SELECT * FROM demo_orders WHERE is_del = 0;
// Delete
UPDATE demo_orders SET is_del = 1 WHERE id = 1;
領(lǐng)域?qū)ο?/h3>
然后是領(lǐng)域?qū)ο?,在logic/do 目錄中新建 demo.go 文件,在其中定義DemoOrder領(lǐng)域?qū)ο?/p>
type DemoOrder struct {
Id int64 `json:"id"`
UserId int64 `json:"user_id"`
BillMoney int64 `json:"bill_money"`
OrderNo string `json:"order_no"`
State int8 `json:"state"`
IsDel uint `json:"is_del"`
PaidAt time.Time `json:"paid_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
可以看到這個(gè)領(lǐng)域?qū)ο蠛蚆odel對(duì)象沒啥區(qū)別,確實(shí)是這樣的,如果Model的字段都有業(yè)務(wù)意義那字段基本上完全一樣,對(duì)于只針對(duì)數(shù)據(jù)庫(kù)有意義的非業(yè)務(wù)字段就沒必要出現(xiàn)在領(lǐng)域?qū)ο笾辛恕?/p>
響應(yīng)對(duì)象
響應(yīng)對(duì)象是針對(duì)客戶端需求的,比如像ID這種在業(yè)務(wù)內(nèi)部才有意義的字段可以選擇不暴露出去,只通過orderNo之類的標(biāo)識(shí)請(qǐng)求后端接口就可以了。
在 api/reply 目錄下我們新建demo.go 并創(chuàng)建響應(yīng)對(duì)象,其跟領(lǐng)域?qū)ο蟮膮^(qū)別是少了id、is_del這種客戶端不需要知道的字段,以及把時(shí)間的類型都換成了字符串,我們?cè)趧?chuàng)建響應(yīng)對(duì)象時(shí)把訂單中的各種時(shí)間格式化成字符串再賦給響應(yīng)對(duì)象,這樣控制器拿到響應(yīng)對(duì)象后直接返回就可以啦。
type DemoOrder struct {
UserId int64 `json:"user_id"`
BillMoney int64 `json:"bill_money"`
OrderNo string `json:"order_no"`
State int8 `json:"state"`
PaidAt string `json:"paid_at"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
請(qǐng)求對(duì)象
此外我們還在 api/request 目錄下的demo.go 中定義了創(chuàng)建訂單的請(qǐng)求對(duì)象
type DemoOrderCreate struct {
UserId int64 `json:"user_id"`
BillMoney int64 `json:"bill_money" binding:"required"`
// 這個(gè)字段演示的時(shí)候因?yàn)闆]創(chuàng)建訂單快照表所以不寫庫(kù)
OrderGoodsId int64 `json:"order_goods_id" binding:"required"`
}
在Controller接收到請(qǐng)求后,它會(huì)利用Gin提供的數(shù)據(jù)驗(yàn)證和綁定幫我們驗(yàn)證請(qǐng)求數(shù)據(jù)然后把它們綁定到請(qǐng)求對(duì)象上。
Copier
這里我們先暫停一下, 很多人可能會(huì)有疑問你搞那么多對(duì)象,到時(shí)候得多寫多少代碼呀?
那么這里我就介紹一下這個(gè)工具"github.com/jinzhu/copier",也是GORM的作者開發(fā)的,它的作用類似于Java的BeanUtils.copyProperties 把源對(duì)象中的字段拷貝到目標(biāo)對(duì)象中去。
我在項(xiàng)目common/util/copy.go中封裝了一個(gè)工具函數(shù)幫我們完成數(shù)據(jù)拷貝,同時(shí)還定義了從時(shí)間對(duì)象轉(zhuǎn)換成時(shí)間字符串的轉(zhuǎn)換器,讓我們?cè)诳截悢?shù)據(jù)的同時(shí)完成time.Time類型字段的格式化。這樣從領(lǐng)域?qū)ο筠D(zhuǎn)換成返回給客戶端使用的響應(yīng)對(duì)象的時(shí)就不需要再手動(dòng)轉(zhuǎn)換了。
使用我們的數(shù)據(jù)轉(zhuǎn)換工具util.CopyProperties后上面的代碼可以直接簡(jiǎn)化成下圖這樣
圖片
使用util.CopyProperties即可完成數(shù)據(jù)對(duì)象的轉(zhuǎn)換,不需要我們?cè)谝粋€(gè)字段一個(gè)字段的去復(fù)制了,也省去了經(jīng)常做的時(shí)間轉(zhuǎn)換的操作。
這個(gè)工具必不可少的會(huì)使用反射來完成數(shù)據(jù)復(fù)制,如果對(duì)性能很敏感,可以自己寫一些領(lǐng)域?qū)ο蟮巾憫?yīng)對(duì)象的Convertor方法,如果量大嫌自己寫的麻煩,可以研究一下用Go編譯的AST ,在編譯時(shí)自動(dòng)生成這些Convertor方法。