共计 2990 个字符,预计需要花费 8 分钟才能阅读完成。
六边形架构与DDD
传统的分层架构
分层架构是一种通用的应用架构,它将应用分为多个独立的功能层,每个层都负责不同的功能,例如表现层、业务逻辑层、数据访问层等。这种架构的好处就是能够提高代码的可维护性、可扩展性,同时也可以降低系统的复杂性。
这种架构在实际开发过程中,会产生一些问题:
-
某些逻辑或某些数据处理该放在哪一层?
-
应该分多少层?
-
平层和跨层调用是否合理?
-
当程序耦合度逐渐增高时,很难或不可能允许该程序被另一个程序驱动
六边形架构
Alistair Cockburn提出了一种解决对称性问题中的应用架构。它可以看做是一种特殊的分层架构,区别在于它把多级的上下层变成了内外层。
在这种架构里,不同的客户通过平等的方式与系统进行交互。它强调应用通过端口与外部进行交互,而外部的实体也可以用同样的方式来处理。所以六边形架构又称“端口适配器架构”。
比如HTTP客户、MQ客户,甚至是测试用例。它们都是平等的对系统提供输入。Redis和DB也是平等的提供输出。每个客户都有自己的适配器,去理解输入。这些输入和输出就是外部的适配器,而内部就是业务系统,开发者可以将重心放在业务逻辑上,隔离输入和输出。
DDD与六边形架构是如何结合的
所有的数据处理全部由repository适配成实体,逻辑都是领域服务、聚合和实体的行为。分多少层和平层、跨层调用都不存在。
项目结构
-
domain – 领域模型
-
aggregate – 聚合
-
dependency – 依赖倒置的接口
-
entity – 实体
-
dto – 传输对象
-
po – 持久化对象
-
*.go – 领域服务
-
-
adapter – 适配器
-
controller – 输入适配器
-
consumer – 输入适配器
-
repository – 输出适配器
-
-
infra – 基础设施
- *.go – 基础设施组件
domain 领域模型
对应六边形的内部,主要放领域服务的代码。子目录分为aggregate聚合根目录,entity实体目录,dto或者vo是外部输入输出对象,po是数据库持久化对象。
adapter 适配器
对应六边形的外部,主要是输入和输出的适配器。controller子目录负责http的请求输入,repository子目录负责实体的读写。
外部adapter目录的controller、repository和consumer依赖内部的domain,domain要使用repository处理po的读写,这样就产生了相互依赖。后续的依赖倒置会讲解如何解除这种依赖(外部依赖内部,内部依赖抽象)。
主要代码参考
// Package controller ..
package controller
import (
...
)
func init() {
dt.Prepare(func(initiator dt.Initiator) {
// 注册外部API
if !initiator.IsPrivate() {
initiator.BindController("/syncplans", &SyncPlan{})
}
})
}
// SyncPlan ..
type SyncPlan struct {
Worker dt.Worker // 运行时对象 依赖注入请求运行时 无序侵入的传递
Request *requests.Request // 基础设施 用于处理客户端请求io的json数据和验证
SyncPlanSrv *domain.SyncPlan // 配置领域服务
Roler role.RoleHandler // 角色管控组件
}
// Get GET:/syncplans/
func (c *SyncPlan) Get() dt.Result {
page := hiveutils.StrToInt(c.Request.ReadQueryDefault("page", "1"))
pageSize := hiveutils.StrToInt(c.Request.ReadQueryDefault("pagesize", "10"))
voItems, totalPage, err := c.SyncPlanSrv.Items(page, pageSize)
if err != nil {
return &requests.JSONResponse{Error: err}
}
c.Worker.IrisContext().Header("X-Total-Page", strconv.Itoa(totalPage))
return &requests.JSONResponse{Object: voItems}
}
// Package domain 领域服务
package domain
import (
...
)
func init() {
// 绑定领域服务到框架,框架会根据客户的使用做依赖倒置和依赖注入的处理
dt.Prepare(func(initiator dt.Initiator) {
initiator.BindService(func() *SyncPlan {
return &SyncPlan{}
})
// 控制器客户需要明确使用 InjectController
initiator.InjectController(func(ctx hive.Context) (service *SyncPlan) {
initiator.GetService(ctx, &service)
return
})
})
}
// SyncPlan 同步计划领域服务
type SyncPlan struct {
Worker dt.Worker // 运行时对象
SyncPlanRepo dependency.SyncPlanRepo // 依赖倒置资源库 DIP Dependency Inversion Principle
Cache store.Cache // 缓存组件
Transaction *domainevent.EventTransaction // 依赖注入事务组件
}
// Items 查询同步计划列表
func (domain *SyncPlan) Items(page, pagesize int) (result []vo.SyncPlanItemRes, totalPage int, err error) {
result = make([]vo.SyncPlanItemRes, 0)
domain.Worker.Logger().Info("ready to get syncplan list...")
entitys, totalPage, err := domain.SyncPlanRepo.Finds(page, pagesize)
if err != nil {
return
}
for i := 0; i < len(entitys); i++ {
item := vo.SyncPlanItemRes{
ID: entitys[i].ID,
SourceDoclibID: entitys[i].SourceDoclibID,
SourceDoclibType: entitys[i].SourceDoclibType,
SourceDoclibName: entitys[i].SourceDoclibName,
TargetDomainID: entitys[i].TargetDomainID,
TargetDoclibName: entitys[i].TargetDoclibName,
Pattern: entitys[i].Pattern,
Status: entitys[i].Status,
}
result = append(result, item)
}
return
}