為 Gopher 打造 DDD 系列:領(lǐng)域模型-六邊形架構(gòu)
前言:六邊形架構(gòu)又稱“端口適配器架構(gòu)”,實(shí)際上也是一種分層架構(gòu),只不過由上下或者左右變成了內(nèi)部與外部。其核心理念就是應(yīng)用通過端口與外部進(jìn)行交互的。核心的業(yè)務(wù)邏輯(領(lǐng)域模型)與外部資源(數(shù)據(jù)庫等資源)完全隔離,僅通過適配器進(jìn)行交互,解決了業(yè)務(wù)邏輯與用戶數(shù)據(jù)交錯(cuò)的問題,很好的實(shí)現(xiàn)了前后端分離。
困惑:
在分層架構(gòu)中是否困惑過某些邏輯處理或某些數(shù)據(jù)處理該放在哪一層? 在分層架構(gòu)中是否困惑過該分多少層? 在分層架構(gòu)中是否困惑過平層和跨層調(diào)用是否合理?
六邊形架構(gòu)
Alistair Cockburn 提出了一種具有對(duì)稱特征的架構(gòu)風(fēng)格。在這種架構(gòu)中,不同的客戶通過平等的方式與系統(tǒng)交互。
比如HTTP客戶,MQ客戶,它們平等對(duì)系統(tǒng)提供輸入。Redis和DB也平等的提供輸出。每個(gè)客戶都擁有自己的適配器,去理解輸入,比如gin、iris、echo就是http的適配器。
那么內(nèi)部是業(yè)務(wù)系統(tǒng)(領(lǐng)域模型),外部就是輸入和輸出的適配器。重心放在內(nèi)部業(yè)務(wù)邏輯上,隔離輸入和輸出。如果非要用分層來理解,那么六邊形分為內(nèi)層和外層。
Alistair Cockburn 提出的六邊形是有Application和Domain的,但現(xiàn)在微服務(wù)體系下Application已經(jīng)沒有存在的必要了,一個(gè)微服務(wù)就是一個(gè)Application。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?落地的六邊形架構(gòu)圖
那么六邊形和DDD的結(jié)合是如何應(yīng)對(duì)上述困惑的。所有數(shù)據(jù)處理全部由repository適配成實(shí)體,邏輯都是領(lǐng)域服務(wù)、聚合、實(shí)體的行為。分多少層和平層、跨層調(diào)用本身也不存在。
項(xiàng)目目錄
domain- 領(lǐng)域模型aggregate- 聚合entity- 實(shí)體dto- 傳輸對(duì)象po- 持久化對(duì)象*.go- 領(lǐng)域服務(wù)adapter- 端口適配器controller- 輸入適配器repository- 輸出適配器server- 服務(wù)端程序入口conf- 配置文件main.go- 主函數(shù)infra- 基礎(chǔ)設(shè)施*go- 基礎(chǔ)設(shè)施組件
domain 領(lǐng)域模型目錄
對(duì)應(yīng)六邊形的內(nèi)部,主要放領(lǐng)域服務(wù)service的代碼。子目錄分為aggregate聚合根目錄、entity實(shí)體目錄。
adapter 適配器目錄
對(duì)應(yīng)六邊形的外部,主要是輸入和輸出的適配器。controller子目錄負(fù)責(zé) http的api輸入,repository子目錄負(fù)責(zé)實(shí)體的讀寫。dto子目錄是controller或repository的外部輸入輸出對(duì)象。po子目錄是數(shù)據(jù)庫的持久化對(duì)象,這些對(duì)象是生成的。
外部adapter目錄下的controller和repository依賴內(nèi)部的domain相關(guān)實(shí)現(xiàn),那么如果domain要使用repository處理po的讀寫呢?這樣不就互相依賴了嗎?
后續(xù)文章《依賴倒置》將講解什么是外部依賴內(nèi)部,內(nèi)部依賴抽象。
代碼示例
package controllerimport (domain "github.com/8treenet/freedom/example/fshop/domain")type Cart struct {Worker freedom.WorkerCartSev *domain.Cart //購物車領(lǐng)域服務(wù)}// GetItems 獲取購物車商品列表, GET: /cart/items route.func (c *Cart) GetItems() freedom.Result {userId, err := c.Worker.IrisContext().URLParamInt("userId")if err != nil {return &infra.JSONResponse{Error: err}}//適配http的輸入?yún)?shù)userId后調(diào)用領(lǐng)域模型目錄的入口領(lǐng)域服務(wù)dto, err := c.CartSev.Items(userId)if err != nil {return &infra.JSONResponse{Error: err}}return &infra.JSONResponse{Object: dto}}領(lǐng)域模型
package domainimport (//引用倉庫"github.com/8treenet/freedom/example/fshop/adapter/repository""github.com/8treenet/freedom/example/fshop/domain/aggregate")// Cart 購物車領(lǐng)域服務(wù).type Cart struct {CartRepo repository.CartRepo //購物車倉庫,這里是依賴倒置的}// Items 購物車全部商品項(xiàng)func (c *Cart) Items(userId int) (items dto.CartItemRes, e error) {// 使用 c.CartRepo讀取購物車數(shù)據(jù)return}// DeleteAll 清空購物車func (c *Cart) DeleteAll(userId int) (e error) {return c.CartRepo.DeleteAll(userId)}
推薦閱讀
站長(zhǎng) polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)
Go語言中文網(wǎng)
每天為你
分享 Go 知識(shí)
Go愛好者值得關(guān)注
