背景:

简洁架构洋葱架构六边形架构
  1. 业务代码与基础设施隔离
  2. 外层设施依赖内层业务代码

以下是他们的简介,有兴趣可以通过最下方的链接查看原文

六边形架构

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

原则

  1. 独立于外部设施,内层不能知道外部的信息
  2. 依赖方向只能往内

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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: