背景:
简洁架构洋葱架构六边形架构
- 业务代码与基础设施隔离
- 外层设施依赖内层业务代码
以下是他们的简介,有兴趣可以通过最下方的链接查看原文
六边形架构
Ports & Adapters阿利斯泰尔·科伯恩 (Alistair Cockburn)
applicationportadapterapplicationDBHTTP
此时的架构还只是一个理论思想,并没有像后来的架构提创的各种分层以及各种名词的定义
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ZZRdQOr-1662273335661)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f29b3ba175084494886c665b76c5c2ab~tplv-k3u1fbpfcp-watermark.image?)]
洋葱架构
杰弗里·巴勒莫 (Jeffrey Palermo)
文章中他指出了洋葱架构和传统架构的差异,传统的三层架构中,我们不会区分业务代码和外部设施同时每个层都会耦合外部设施,万一外部设施发生改变,每个层涉及的代码都会发生改变,此处指的外部设施包括(日历,db,远程调用等)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-znTAaiTF-1662273335662)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5cf0038002bf4118859d344971fd4b30~tplv-k3u1fbpfcp-watermark.image?)]
提出了洋葱架构的同时还贴心的提出了翻译版本的图
application
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JEX3o1A6-1662273335663)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ef025913dccb4f6f9ca836c67297bd05~tplv-k3u1fbpfcp-watermark.image?)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ugQdNY2-1662273335663)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/69078e79b6fb4083a0b8d6879002b234~tplv-k3u1fbpfcp-watermark.image?)]
整洁架构
罗伯特·C·马丁 (Robert Cecil Martin)EntitiesUse CasesControllers,Gateways等Web,DB等
webControllersUse CasesEntitiesGatewaysDB
原则
- 独立于外部设施,内层不能知道外部的信息
- 依赖方向只能往内
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lA3cIBwf-1662273335664)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e0b2d16827be4bb585fc35ad972cc5d8~tplv-k3u1fbpfcp-watermark.image?)]
Entities
集合部分业务规则,既可以是有方法的对象,也可以是一组数据结构或者方法的组合
Use Cases
包含应用程序的业务规则,封装并实现了所用的用例, 负责编排实体的数据更新或者使用他们的特定业务逻辑去实现目的
这一层的变动不应该影响entity,但它也不应该受ui,database,框架的影响而改变
Controllers, GateWays
主要是 port 和 adapter 适配器功能,用于将数据转化为更适合外圈数据需要的层
同时这一层也将外部数据 database, http 转化为 entity 的作用
到了这里基本把三个架构都简单的介绍了一遍,后面就是按照上面介绍的思想,用于实践到我们的项目中来
项目目录
| -- main.go # 项目启动入口, 调用service各业务的new方法
| -- entity
| -- log # 定义常用的通用方法,例如: 日志,隔离核心代码与外部设施的依赖
| -- gift # 每个业务定义一个文件夹,存在各个实体
| -- logic
| -- gift # 每个业务定义一个文件夹
| -- port.go # 定义接口,供service调用
| -- adapter.go # port的具体实现
| -- repository
| -- gift
| -- adapter.go # 实现gateway的port
| -- assembler.go # entity与外部设施(mysql,redis,http)沟通的数据结构转换器,非必须
| -- mysql # mysql/redis/http 视各自业务而定
| -- port.go # 接口文件
| -- service # 服务入口,建议各自业务定义一个文件
| -- gift.go # 调用logic的interface, 发起提问的作用
Example
这里我写了一个例子 送礼物
里面会包括基本的分层,日志,db,外部接口调用等例子
giftgift_configgiftgift_config
AddGiftConfig
func (a *Adapter) AddGiftConfig(ctx context.Context, cmd AddGiftConfigCMD) error {
entity := &giftEntity.ConfigEntity{}
if err := a.giftConfigPort.Create(ctx, entity); err != nil {
log.ErrorContextf(ctx, "xxx error:", err)
return err
}
// 更新缓存
_ = async.Go(ctx, 3*time.Second, func(cloneCtx context.Context) {
_ = a.giftConfigPort.RefreshCache(cloneCtx)
})
return nil
}
AddGift
func (a *Adapter) AddGift(ctx context.Context, uin uint64) error {
configs := a.giftConfigPort.List(ctx, giftconfig.GiftConfigQRY{})
for _, c := range configs {
if c.AllowSendGift() {
insertData := &giftEntity.Entity{}
if err := a.giftPort.Create(ctx, insertData); err != nil {
return err
}
return nil
}
}
return errors.New("not allow gift")
}
总结
虽然分层的名称,概念各不相同,但是大家都有意将业务代码与外部依赖隔离开,并且依赖方向是向内的,只要把握这两条原则,其他就可以自己根据业务需要各自实现了
Reference: