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