• go简洁架构实践


    背景:

    说起简洁架构,不得不先说另外两个也很著名的架构 洋葱架构六边形架构,它们都有共同的思想

    1. 业务代码与基础设施隔离
    2. 外层设施依赖内层业务代码

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

    六边形架构

    又名 Ports & Adapters阿利斯泰尔·科伯恩 (Alistair Cockburn)提出,如名称显示,就是端口与适配器实现架构为的是把业务代码与基础设施隔离,核心逻辑与外部依赖隔离.

    如下显示,application不与外部接触,所以交流都通过portadapter来实现,即使外部设施发生改变也不会影响到application, 同时由于不依赖外部设施,在进行单元测试的时候,即使没有DBHTTP等其他外部设施也能进行

    此时的架构还只是一个理论思想,并没有像后来的架构提创的各种分层以及各种名词的定义

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

    提出了洋葱架构的同时还贴心的提出了翻译版本的图

    1. 外层依赖内层
    2. 项目依赖model
    3. 内层定义接口,外层实现
    4. application可以在缺少外部设施下运行
    5. 外层可以调用所有的内层,而并非如传统架构中,只能使用自己的下一层

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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) 提出,此时已经可以把我们的项目划分为基本的四层架构,分别是核心Entities,Use Cases,为了跟外部设施进行交流所做的适配器层Controllers,Gateways等,最后就是外部设施Web,DB等

    一个最基本的工作流,应该是 web -> Controllers -> Use Cases -> Entities -> Gateways -> DB,然后依次返回

    原则

    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, 发起提问的作用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    Example

    这里我写了一个例子 送礼物
    里面会包括基本的分层,日志,db,外部接口调用等例子

    假设我们有一个项目叫做送礼物,其中有个两个模块 giftgift_config, gift负责给某个人送礼物,gift_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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    总结

    虽然分层的名称,概念各不相同,但是大家都有意将业务代码与外部依赖隔离开,并且依赖方向是向内的,只要把握这两条原则,其他就可以自己根据业务需要各自实现了

    Reference:

    The Clean Code Blog

    Golang 简洁架构实战

    The Onion Architecture

    Hexagonal architecture

  • 相关阅读:
    在win7 上安装 Visual Studio 2019 步骤 及 vs2019离线安装包
    Linux——文件传输协议知识点梳理
    华纳云:远程桌面服务器出现乱码是什么原因?
    Vue学习笔记(一)——搭建自己的Vue项目及框架结构解释
    MetaAI的融合怪:BlenderBot
    Go项目目录结构介绍
    部署你自己的导航站-dashy
    【微信小程序】手把手教你注册开发账号、安装开发工具、使用开发工具
    Spring及Spring boot 第四章-第二节 Spring声明式事务管理 拦截过程
    十、性能测试之数据库测试
  • 原文地址:https://blog.csdn.net/luo1324574369/article/details/126689235