前言:六边形架构又称“端口适配器架构”,实际上也是一种分层架构,只不过由上下或者左右变成了内部与外部。其核心理念就是应用通过端口与外部进行交互的。核心的业务逻辑(领域模型)与外部资源(数据库等资源)完全隔离,仅通过适配器进行交互,解决了业务逻辑与用户数据交错的问题,很好的实现了前后端分离。
困惑:
- 在分层架构中是否困惑过某些逻辑处理或某些数据处理该放在哪一层?
- 在分层架构中是否困惑过该分多少层?
- 在分层架构中是否困惑过平层和跨层调用是否合理?
六边形架构
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
实体目录。dto
子目录是外部输入输出对象。po
子目录是数据库的持久化对象,这些对象是生成的。
adapter 适配器目录
对应六边形的外部,主要是输入和输出的适配器。controller
子目录负责http
的api
输入,repository
子目录负责实体的读写。
外部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"
)
func init() {
freedom.Prepare(func(initiator freedom.Initiator) {
//绑定创建领域服务函数到框架,框架会根据客户的使用做依赖倒置和依赖注入的处理。
initiator.BindService(func() *Cart {
//创建 Cart领域服务
return &Cart{}
})
//控制器客户使用需要明确使用 InjectController
initiator.InjectController(func(ctx freedom.Context) (service *Cart) {
initiator.GetService(ctx, &service)
return
})
})
}
// 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)
}
目录
- golang领域模型-开篇
- golang领域模型-六边形架构
- golang领域模型-实体
- golang领域模型-资源库
- golang领域模型-依赖倒置
- golang领域模型-聚合根
- golang领域模型-CQRS
- golang领域模型-领域事件
项目代码 https://github.com/8treenet/freedom/tree/master/example/fshop
PS:关注公众号《从菜鸟到大佬》,发送消息“加群”或“领域模型”,加入DDD交流群,一起切磋DDD与代码的艺术!