为 Gopher 打造 DDD 系列:领域模型-六边形架构
前言:六边形架构又称“端口适配器架构”,实际上也是一种分层架构,只不过由上下或者左右变成了内部与外部。其核心理念就是应用通过端口与外部进行交互的。核心的业务逻辑(领域模型)与外部资源(数据库等资源)完全隔离,仅通过适配器进行交互,解决了业务逻辑与用户数据交错的问题,很好的实现了前后端分离。
困惑:
在分层架构中是否困惑过某些逻辑处理或某些数据处理该放在哪一层? 在分层架构中是否困惑过该分多少层? 在分层架构中是否困惑过平层和跨层调用是否合理?
六边形架构
Alistair Cockburn 提出了一种具有对称特征的架构风格。在这种架构中,不同的客户通过平等的方式与系统交互。
比如HTTP
客户,MQ
客户,它们平等对系统提供输入。Redis
和DB
也平等的提供输出。每个客户都拥有自己的适配器,去理解输入,比如gin
、iris
、echo
就是http
的适配器。
那么内部是业务系统(领域模型),外部就是输入和输出的适配器。重心放在内部业务逻辑上,隔离输入和输出。如果非要用分层来理解,那么六边形分为内层和外层。
Alistair Cockburn 提出的六边形是有Application和Domain
的,但现在微服务体系下Application已经没有存在的必要了,一个微服务就是一个Application
。
落地的六边形架构图
那么六边形和DDD的结合是如何应对上述困惑的。所有数据处理全部由repository
适配成实体,逻辑都是领域服务、聚合、实体的行为。分多少层和平层、跨层调用本身也不存在。
项目目录
domain
- 领域模型aggregate
- 聚合entity
- 实体dto
- 传输对象po
- 持久化对象*.go
- 领域服务adapter
- 端口适配器controller
- 输入适配器repository
- 输出适配器server
- 服务端程序入口conf
- 配置文件main.go
- 主函数infra
- 基础设施*go
- 基础设施组件
domain 领域模型目录
对应六边形的内部,主要放领域服务service
的代码。子目录分为aggregate
聚合根目录、entity
实体目录。
adapter 适配器目录
对应六边形的外部,主要是输入和输出的适配器。controller
子目录负责 http
的api
输入,repository
子目录负责实体的读写。dto
子目录是controller
或repository
的外部输入输出对象。po
子目录是数据库的持久化对象,这些对象是生成的。
外部adapter
目录下的controller
和repository
依赖内部的domain
相关实现,那么如果domain
要使用repository
处理po
的读写呢?这样不就互相依赖了吗?
后续文章《依赖倒置》将讲解什么是外部依赖内部,内部依赖抽象。
代码示例
package controller
import (
domain "github.com/8treenet/freedom/example/fshop/domain"
)
type Cart struct {
Worker freedom.Worker
CartSev *domain.Cart //购物车领域服务
}
// 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的输入参数userId后调用领域模型目录的入口领域服务
dto, err := c.CartSev.Items(userId)
if err != nil {
return &infra.JSONResponse{Error: err}
}
return &infra.JSONResponse{Object: dto}
}
领域模型
package domain
import (
//引用仓库
"github.com/8treenet/freedom/example/fshop/adapter/repository"
"github.com/8treenet/freedom/example/fshop/domain/aggregate"
)
// Cart 购物车领域服务.
type Cart struct {
CartRepo repository.CartRepo //购物车仓库,这里是依赖倒置的
}
// Items 购物车全部商品项
func (c *Cart) Items(userId int) (items dto.CartItemRes, e error) {
// 使用 c.CartRepo读取购物车数据
return
}
// DeleteAll 清空购物车
func (c *Cart) DeleteAll(userId int) (e error) {
return c.CartRepo.DeleteAll(userId)
}
推荐阅读
站长 polarisxu
自己的原创文章
不限于 Go 技术
职场和创业经验
Go语言中文网
每天为你
分享 Go 知识
Go爱好者值得关注