64.Go整洁代码架构实践

news2024/10/2 14:34:40

文章目录

  • 一、为什么要有代码架构
  • 二、好的代码架构是如何构建的
    • 1、整洁架构
    • 2、洋葱架构
    • 三、六边形架构
    • 4、COLA (Clean Object-oriented and Layered Architecture)架构
  • 三、Go 代码架构实践
    • 1、目录设计
    • 2、Adapter 层 : 负责http路由或者rpc接口管理
    • 3、Application层:负责业务逻辑处理,定义各种service或handler
    • 4、Domain层:定义核心业务接口和model,但不具体实现
    • 5、Infrastructure 层:对Domain层接口的具体实现
  • 四、总结

本文提及的架构主要指项目组织的“代码架构”,注意与微服务架构等名词中的服务架构进行区分。

目前主流的代码目录组织结构有 Hex 六边形架构Onion 洋葱架构Clean 整洁架构三种。

一、为什么要有代码架构

历史悠久的项目大都会有很多开发人员参与“贡献”,在没有好的指导规则约束的情况下,大抵会变成一团乱麻。剪不断,理还乱,也没有勇士开发者愿意去剪去理。被迫接手的勇士开发者如果想要增加一个小需求,可能需要花 10 倍的时间去理顺业务逻辑,再花 10 倍的时间去补充测试代码,实在是低效又痛苦。

这是一个普遍的痛点问题,也有无数开发者尝试过去解决它。这么多年发展累积下来,业界自然也诞生了很多软件架构。大家耳熟能详的就有六边形架构(Hexagonal Architecture)洋葱架构(Onion Architecture)整洁架构(Clean Architecture)等。这些架构在细节上肯定有所差异,但是核心目标都是一致的:致力于实现软件系统的关注点分离(separation of concerns)

关注点分离之后的软件系统都具备如下特征:

  • 不依赖特定 UIUI 可以任意替换,不会影响系统重其他组件。从 Web UI 变成桌面 UI,甚至变成控制台 UI 都无所谓,业务逻辑不会被影响。

  • 不依赖特定框架。以 go web 生态举例,不管是使用 gin 框架还是beego框架甚至iris框架,业务逻辑都不会被影响,被影响的只会是框架接入的那一层。

  • 不依赖特定外部组件。系统可以任意使用 MySQL, MongoDB, 或 Neo4j 作为数据库,任意使用 Redis, Memcached, 或 etcd 作为键值存储等。业务逻辑不会因为这些外部组件的替换而变化。

  • 容易测试。核心业务逻辑可以在不需要 UI,不需要数据库,不需要 Web 服务器等一切外界组件的情况下被测试。这种纯粹的代码逻辑意味着清晰容易的测试。

软件系统有了这些特征后,易于测试,更易于维护、更新,大大减轻了软件开发人员的心智负担。所以,好的代码架构确实值得推崇。

二、好的代码架构是如何构建的

前文所述的三个架构在理念上是近似的,从接下来的介绍中一定不难看出他们具有相似的圈层结构。图中可以看到,越往外层越具体,越往内层越抽象。这也意味着,越往外越有可能发生变化,包括但不限于框架升级,中间件变更,适配新终端等等。

1、整洁架构

在这里插入图片描述

整洁架构的同心圆结构中可以看见三条由外向内的黑色箭头,它表示依赖规则(The Dependency Rule)。依赖规则规定外层的代码可以依赖内层,但是内层的代码不可以依赖外层。也就是说内层逻辑不可以依赖任何外层定义的变量,函数,结构体,类,模块等等代码实体。假如说,最外层蓝色层“Frameworks & Drivers & DB 处使用了 go 语言的 gorm 三方库,并定义了 gorm 相关的数据库结构体及其 tag 等。那么内层的 Gateways,Use Cases, Entities 等处不可以引用任何外层中 gorm 相关的结构体或方法,甚至不应该感知到gorm的存在。

核心层的Entities定义表示核心业务规则的核心业务实体。这些实体既可以是带方法的类,也可以是带有一堆函数的结构体。但它们必须是高度抽象的,只可以随着核心业务规则变化,不可以随着外层组件的变化而变化。以简单博客系统举例的话,此层可以定义 BlogComment 等核心业务实体。

type Blog struct {...}
type Comment struct {...}

核心层的外层是应用业务层。

应用业务层的 Use Cases 应该包含软件系统所有的业务逻辑。该层控制所有流向和流出核心层的数据流,并使用核心层的实体及其业务规则来完成业务需求。此层的变更不会影响核心层,更外层的变更,比如开发框架、数据库、UI 等变化,也不会影响此层。接着博客系统的例子,此层可以定义 BlogManager 接口,并定义其中的 CreateBlog, LeaveComment 等业务逻辑方法。

type BlogManager interface {
    CreateBlog(...) ...
    LeaveComment(...) ...
}

应用业务层的外层是接口适配层。

接口适配层的 Controllers 将外层输入的数据转换成内层 Use CasesEntities 方便使用的格式,然后 Presenters,Gateways 再将内层处理结果转换成外层方便使用的格式,然后再由更外层呈现到 Web, UI 或者写入到数据库。假如系统选择关系型数据库作为其持久化方案的话,那么所有关于 SQL 的处理都应该在此层完成,更内层不需要感知到任何数据库的存在。同理,假如系统与外界服务通信的话,那么所有有关外界服务数据的转化都在此层完成,更内层也不需要感知到外界服务的存在。外层通过此层传递数据一般通过 DTO(Data Transfer Object)或者 DO(Data Object)完成。接上文博客系统例子,示例代码如下:

type BlogDTO struct { // Data Transfer Object
    Content string `json:"..."`
}

// DTO 与 model.Blog 的转化在此层完成

func CreateBlog(b *model.Blog) {
 dbClient.Create(&blog{...})
 ...
}

接口适配层的外层是处在最外层的框架和驱动层。

该层包含具体的框架和依赖工具细节,比如系统使用的数据库,Web 框架,消息队列等等。此层主要帮助外部框架、工具和内层进行数据衔接。接博客系统例子,框架和驱动层如果使用 gorm 来操作数据库,则相关的示例代码如下:

import "gorm.io/driver/mysql"
import "gorm.io/gorm"

type blog struct { // Data Object
    Content string `gorm:"..."` // 本层的数据库 ORM 如果替换,此处的 tag 也需要随之改变
}
type MySQLClient struct { DB *gorm.DB }
func New(...) { gorm.Open(...) ... }
func Create(...)
...

至此,整洁架构图中的四层已介绍完成。但此图中的四层结构仅作示意,整洁架构并不要求软件系统必须严格按照此四层结构。只要软件系统能保证“由外向内”的依赖规则,系统的层数多少可自由裁决。

2、洋葱架构

在这里插入图片描述

洋葱架构与整洁架构相似,整体结构也是四层同心圆。

洋葱架构最核心的Domain Model表示组织中核心业务的状态及其行为模型,与整洁架构中的 Entities 高度一致。其外层的 Domain Services 与整洁架构中的 Use Cases 职责相近。更外层的 Application Services 桥接 UIInfrastructue 中的数据库、文件、外部服务等,更是与整洁架构中的 Interface Adaptors 功能相同。最边缘层的 User Interface 与整洁架构中的最外层 UI 部分一致,Infrastructure 则与整洁架构中的 DB, Devices, External Interfaces 作用一致,只 Tests 部分稍有差异。

三、六边形架构

在这里插入图片描述

六边形架构中灰色箭头表示依赖注入(Dependency Injection),其与整洁架构中的依赖规则(The Dependency Rule)有异曲同工之妙,也限制了整个架构各组件的依赖方向必须是“由外向内”。图中的各种 PortAdapter 是六边形架构的重中之重,故该架构别称 Ports and Adapters

4、COLA (Clean Object-oriented and Layered Architecture)架构

其实,不仅国外有优秀的代码架构,国内也有。

国内开发者在学习了六边形架构,洋葱架构和整洁架构之后,提出了 COLA (Clean Object-oriented and Layered Architecture)架构,其名称含义为“整洁的基于面向对象和分层的架构”。它的核心理念与国外三种架构相同,都是提倡以业务为核心,解耦外部依赖,分离业务复杂度和技术复杂度,如下图
在这里插入图片描述
虽然 COLA 架构不再是同心圆或者六边形的形式,但是还是能明显看到前文三种架构的影子。Domain 层中 model 对应整洁架构的 Entities,六边形架构和洋葱架构中的 Domain ModelDomain 层中 gatewayability 对应整洁架构的 Use Cases,六边形架构中的 Application Logic,以及洋葱架构中的 Domain ServicesApp 层则对应整洁架构 Interface Adapters 层中的 Controllers,Gateways,和 Presenters。最上方的 Adapter 层和最下方的 Infrastructure 层合起来与整洁架构的边缘层 Frameworks & Drivers 相呼应。

Adapter 层上方的 Driving adaterInfrastructure 层下方的 Driven adapter 更是与六边形架构中的 Driving SideDriven Side 高度一致。

三、Go 代码架构实践

1、目录设计

├── adapter // Adapter层,适配各种框架及协议的接入,比如:Gin,tRPC,Echo,Fiber 等
├── application // App层,处理Adapter层适配过后与框架、协议等无关的业务逻辑
│   ├── consumer //(可选)处理外部消息,比如来自消息队列的事件消费
│   ├── dto // App层的数据传输对象,外层到达App层的数据,从App层出发到外层的数据都通过DTO传播
│   ├── executor // 处理请求,包括command和query
│   └── scheduler //(可选)处理定时任务,比如Cron格式的定时Job
├── domain // Domain层,最核心最纯粹的业务实体及其规则的抽象定义
│   ├── gateway // 领域网关,model的核心逻辑以Interface形式在此定义,交由Infra层去实现
│   └── model // 领域模型实体
├── infrastructure // Infra层,各种外部依赖,组件的衔接,以及domain/gateway的具体实现
│   ├── cache //(可选)内层所需缓存的实现,可以是Redis,Memcached等
│   ├── client //(可选)各种中间件client的初始化
│   ├── config // 配置实现
│   ├── database //(可选)内层所需持久化的实现,可以是MySQL,MongoDB,Neo4j等
│   ├── distlock //(可选)内层所需分布式锁的实现,可以基于Redis,ZooKeeper,etcd等
│   ├── log // 日志实现,在此接入第三方日志库,避免对内层的污染
│   ├── mq //(可选)内层所需消息队列的实现,可以是Kafka,RabbitMQ,Pulsar等
│   ├── node //(可选)服务节点一致性协调控制实现,可以基于ZooKeeper,etcd等
│   └── rpc //(可选)广义上第三方服务的访问实现,可以通过HTTP,gRPC,tRPC等
└── pkg // 各层可共享的公共组件代码

由此目录结构可以看出通过 Adapter 层屏蔽外界框架、协议的差异,Infrastructure 层囊括各种中间件和外部依赖的具体实现,application 层负责组织输入输出, Domain 层可以完全聚焦在最纯粹也最不容易变化的核心业务规则上。

按照前文 infrastructure 中目录结构,各子目录中文件样例参考如下:

├── infrastructure
│   ├── cache
│   │   └── redis.go // Redis 实现的缓存
│   ├── client
│   │   ├── kafka.go // 构建 Kafka client
│   │   ├── mysql.go // 构建 MySQL client
│   │   ├── redis.go // 构建 Redis client(cache和distlock中都会用到 Redis,统一在此构建)
│   │   └── zookeeper.go // 构建 ZooKeeper client
│   ├── config
│   │   └── config.go // 配置定义及其解析
│   ├── database
│   │   ├── dataobject.go // 数据库操作依赖的数据对象
│   │   └── mysql.go // MySQL 实现的数据持久化
│   ├── distlock
│   │   ├── distributed_lock.go // 分布式锁接口,在此是因为domain/gateway中没有直接需要此接口
│   │   └── redis.go // Redis 实现的分布式锁
│   ├── log
│   │   └── log.go // 日志封装
│   ├── mq
│   │   ├── dataobject.go // 消息队列操作依赖的数据对象
│   │   └── kafka.go // Kafka 实现的消息队列
│   ├── node
│   │   └── zookeeper_client.go // ZooKeeper 实现的一致性协调节点客户端
│   └── rpc
│       ├── dataapi.go // 第三方服务访问功能封装
│       └── dataobject.go // 第三方服务访问操作依赖的数据对象

再接前文提到的博客系统例子,假设用 Gin 框架搭建博客系统 API 服务的话,架构各层相关目录内容大致如下:

2、Adapter 层 : 负责http路由或者rpc接口管理

// Adapter 层 router.go,路由入口
import (
    "mybusiness.com/blog-api/application/executor" // 向内依赖 application 层

    "github.com/gin-gonic/gin"
)

func NewRouter(...) (*gin.Engine, error) {
  r := gin.Default()
  r.GET("/blog/:blog_id", getBlog)
  ...
}

func getBlog(...) ... {
  // b's type: *executor.BlogOperator  属于application层定义的类型
  result := b.GetBlog(blogID)
  // c's type: *gin.Context
  c.JSON(..., result)
}

如代码所体现,Gin 框架的内容全部会被限制在 Adapter 层,其他层不会感知到该框架的存在。

3、Application层:负责业务逻辑处理,定义各种service或handler

// App 层 executor/blog_operator.go
import "mybusiness.com/blog-api/domain/gateway" // 向内依赖 Domain 层

type BlogOperator struct {
  blogManager gateway.BlogManager // 字段 类型gateway.BlogManager是接口类型,通过 Infra 层具体实现进行依赖注入
}

func (b *BlogOperator) GetBlog(...) ... {
    blog, err := b.blogManager.Load(ctx, blogID)
    ...
    return dto.BlogFromModel(...) // 通过 DTO 传递数据到外层
}

App 层会依赖 Domain 层定义的领域网关,而领域网关接口会由Infra层的具体实现注入。外层调用 App 层方法,通过 DTO 传递数据,App 层组织好输入交给 Domain 层处理,再将得到的结果通过 DTO 传递到外层。

4、Domain层:定义核心业务接口和model,但不具体实现

// Domain 层 gateway/blog_manager.go
import "mybusiness.com/blog-api/domain/model" // 依赖同层的 model

type BlogManager interface { //定义核心业务逻辑的接口方法
  Load(...) ...
  Save(...) ...
  ...
}

Domain 层是核心层,不会依赖任何外层组件,只能层内依赖。这也保障了 Domain 层的纯粹,保障了整个软件系统的可维护性。

5、Infrastructure 层:对Domain层接口的具体实现

// Infrastructure 层 database/mysql.go
import (
    "mybusiness.com/blog-api/domain/model" // 依赖内层的 model
    "mybusiness.com/blog-api/infrastructure/client" // 依赖同层的 client
)

type MySQLPersistence struct {
  client client.SQLClient // client 中已构建好了所需客户端,此处不用引入 MySQL, gorm 相关依赖
}

func (p ...) Load(...) ... { // Domain 层 gateway 中接口方法的实现
  record := p.client.FindOne(...)
  return record.ToModel() // 将 DO(数据对象)转成 Domain 层 model
}

Infrastructure 层中接口方法的实现都需要将结果的数据对象转化成 Domain model 返回,因为领域网关 gateway 中定义的接口方法的入参、出参只能包含同层的 model,不可以有外层的数据类型。

前文提及的 Blog 读取过程时序示意图如下: 注意其中Domain是接口,Infra是具体实现
在这里插入图片描述

如图,外部请求首先抵达 Adapter 层。如果是读请求,则携带简单参数调用 App 层;如果是写请求,则携带 DTO 调用 App 层。App 层将收到的 DTO 转化成对应的 Model,调用 Domain gateway 相关业务逻辑接口方法。由于系统初始化阶段已经完成依赖注入,接口对应的来自 Infra 层的具体实现会处理完成并返回 ModelDomain 层,再由 Domain 层返回到 App 层,最终经由 Adapter 层将响应内容呈现给外部。

至此可知,参照 COLA 设计的系统分层架构可以一层一层地将业务请求剥离干净,分别处理后再一层一层地组装好返回到请求方。各层之间互不干扰,职责分明,有效地降低了系统组件之间的耦合,提升了系统的可维护性。

四、总结

无论哪种架构都不会是项目开发的银弹,也不会有百试百灵的开发方法论。毕竟引入一种架构是有一定复杂度和较高维护成本的,所以开发者需要根据自身项目类型判断是否需要引入架构。

不建议引入架构的项目类型:

  • 软件生命周期大概率会小于三个月的

  • 项目维护人员在现在以及可见的将来只有自己的

可以考虑引入架构的项目类型:

  • 软件生命周期大概率会大于三个月的

  • 项目维护人员多于 1 人的

强烈建议引入架构的项目类型:

  • 软件生命周期大概率会大于三年的

  • 项目维护人员多于 5 人的

本文参考:https://mp.weixin.qq.com/s/I2Fx2TIrwXV2kfLj_T5g5g

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1362318.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于谷歌Gemini大模型

2023年12月7日,谷歌AI宣布发布新一代基于Transformer架构的大模型Gemini。 Gemini的名字来源于双子座,象征着模型的双重性质: 一方面,它是一个强大的训练模型,可以在各种下游任务上进行微调,如文本摘要、机…

真空引水罐 虹吸抽水机 负压虹吸罐 农业灌溉工作原理动画介绍

​ 1:真空引水罐虹吸抽水机虹吸罐介绍 真空引水罐是一种水泵吸水设备,也被称为真空罐、吸水罐或自动引水装置。它是一个密封的罐体,被串联在泵前的吸水管上,能够使水泵的吸水口从负压吸水变为正压吸水。使用真空引水罐可以节省真…

[蓝桥 2023 ]三带一

问题描述 小蓝和小桥玩斗地主,小蓝只剩四张牌了,他想知道是否是“三带一”牌型。 所谓“三带一”牌型,即四张手牌中,有三张牌一样,另外一张不与其他牌相同,换种说法,四张手牌经过重新排列后&am…

前端结合MQTT实现连接 订阅发送信息等操作 VUE3

MQTT客户端下载 使用测试 在我之前文章中 MQTT下载基础使用 下面记录一下前端使用的话的操作 1.安装 npm i mqtt引入 import * as mqtt from "mqtt/dist/mqtt.min"; //VUE3 import mqtt from mqtt //VUE2 一、MQTT协议中的方法 Connect。等待与服务器建立连接…

体系化的进阶学习内容

UWA学堂:传播游戏行业的体系化的进阶学习内容。UWA学堂作为面向开发者的在线学习平台,目前已经上线272门课程,涵盖了3D引擎渲染、UI、逻辑代码等多个模块,拥有完整的学习体系,一直致力于为广大的开发者提供更丰富、更优…

线程同步及互斥锁

一、线程同步 1. 线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进 行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处 于等待状态。 2. 临界区是指访问某…

junit单元测试:使用@ParameterizedTest 和 @CsvSource注解简化单元测试方法

在平常的开发工作中,我们经常需要写单元测试。比如,我们有一个校验接口,可能会返回多种错误信息。我们可以针对这个接口,写多个单元测试方法,然后将其场景覆盖全。那么,怎么才能写一个测试方法,…

CSDN的Markdown编辑器使用教程

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

YOLOv5改进 | 2023 | SCConv空间和通道重构卷积(精细化检测,又轻量又提点)

一、本文介绍 本文给大家带来的改进内容是SCConv,即空间和通道重构卷积,是一种发布于2023.9月份的一个新的改进机制。它的核心创新在于能够同时处理图像的空间(形状、结构)和通道(色彩、深度)信息,这样的处理方式使得SCConv在分析图像时更加精细和高效。这种技术不仅适…

【Flink精讲】双流Join之Regular Join(即普通Join)

Regular Join 普通Join 通过条件关联两条实时数据流:动态表Join动态表支持Inner Join、Left Join、Right Join、Full Join。 1. Inner Join(Join):只有两边数据流都关联上才输出[L,R] 2. Left Join(Left Outer Join):只要左流有数据即输出[…

听GPT 讲Rust源代码--compiler(26)

File: rust/compiler/rustc_target/src/abi/call/mips.rs 在Rust源代码中的rust/compiler/rustc_target/src/abi/call/mips.rs文件是关于MIPS架构的函数调用ABI(Aplication Binary Interface)定义。ABI是编程语言与底层平台之间的接口规范,用于定义函数调用、参数传…

三英战吕布web3游戏项目启动全流程

项目是一个学习相关的很好的例子并且开源,原本的项目是连接goerli网络,但我把它修改为可连接ganache网络的项目了,更方便启动。 智能合约部分 进入文件 hardhat.config.js ,增加一个钱包私钥 2.执行npm install 3.测试合约 npx ha…

乔布斯在斯坦福大学经典演讲

今天,很荣幸来到各位从世界上最好的学校之一毕业的毕业典礼上。我从来没从大学毕业过,说实话,这是我离大学毕业最近的一刻。 今天,我只说三个故事,不谈大道理,三个故事就好。 第一个故事,是关于…

机器人相关知识

机器人学(Robotics) 一些基础概念 位姿 位姿位置姿态 位姿的表示 刚体 刚性物体是一组粒子的集合,其中任意两个粒子之间的距离保持固定,不受物体运动或施加在物体上的力的影响。 “完全不可变形”的物体就是刚体。 刚体位置 刚性连杆 …

web——德州扑克

1.此案例只用于学习 2.未接入游戏规则 HTML代码部分 <!DOCTYPE html> <html><head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width"><meta name"Poker Skin" content&quo…

YOLOv5改进 | 注意力篇 | Deformable-LKA(DLKA)可变形的大核注意力(附多个位置添加教程)

一、本文介绍 本文给大家带来的改进内容是Deformable-LKA(可变形大核注意力)。Deformable-LKA结合了大卷积核的广阔感受野和可变形卷积的灵活性,有效地处理复杂的视觉信息。这一机制通过动态调整卷积核的形状和大小来适应不同的图像特征,提高了模型对目标形状和尺寸的适应…

转转服务瘦身实战

文章目录 1 背景2 第一步-发现并下掉僵尸服务2.1 如何发现僵尸服务2.2 如何下掉僵尸服务 3 第二步-发现并下掉僵尸方法3.1 如何发现僵尸方法3.1.1 全量方法的获取3.1.2 活动方法的获取3.1.3 ServiceAbility Agent方案详解3.1.3.1 ServiceAbility Agent使用方法3.1.3.2 解决stop…

文本批量操作技巧:告别繁琐让办公更轻松,批量添加内容的方法

在工作中&#xff0c;经常要处理大量的文本数据&#xff0c;如文档、电子表格、邮件等。如果一个个地手动编辑&#xff0c;不仅效率低下&#xff0c;还容易出错。现代办公软件提供了许多批量操作技巧&#xff0c;可以轻松完成大量文本的处理。下面一起来看下“办公提效工具”如…

计算机Java项目|基于SpringBoot+Vue的图书个性化推荐系统

项目编号&#xff1a;L-BS-GX-10 一&#xff0c;环境介绍 语言环境&#xff1a;Java: jdk1.8 数据库&#xff1a;Mysql: mysql5.7 应用服务器&#xff1a;Tomcat: tomcat8.5.31 开发工具&#xff1a;IDEA或eclipse 二&#xff0c;项目简介 图片管理系统是一个为学生和…

二叉搜索树介绍以及实现

二叉树无论是在实际运用还是面试题中&#xff0c;都是一种十分热门的数据结构&#xff0c;而二叉搜索树则是进阶版的二叉树&#xff0c;在map和set中也有应用。 什么是二叉搜索树 二叉搜索树又叫二叉排序树&#xff0c;它可以是一颗空树&#xff0c;又或者是有以下三个特点的…