为 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 controllerimport (domain "github.com/8treenet/freedom/example/fshop/domain")type Cart struct {Worker freedom.WorkerCartSev *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 domainimport (//引用仓库"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爱好者值得关注
