语雀:https://www.yuque.com/konglingfei-vzag4/onex/rh0wrr5rsnyb1qyd
在项目开发中,我们还需要设计合理的代码结构。一个好的代码结构可以降低项目后期的维护成本,并使得项目更加易读。代码结构中,比较重要的设计项有以下 2 项:
- 目录结构;
- 代码架构。
接下来,我就来详细介绍下。
设计合理的目录结构
目录结构通常是指我们的代码由哪些目录组成,每个目录下存放什么文件、实现什么功能,以及各个目录间的依赖关系等。
在项目开发中,目录结构直接决定了项目后期的维护难易度,以及项目的功能组织结构。所以,我们非常有必要设计一个合理的目录结构。在 Go 项目开发中,并没有一个官方的目录结构设计。但 Go 社区有一些推荐的目录结构设计方式,当前最受欢迎、最被大家接受的目录结构设计方式是 project-layout 目录结构设计(简体中文介绍)。
下图是 project-layout 中建议的目录。为了方便你查看,我按功能类别进行了整理:
至于,每个目录存放的功能,及目录的组织方式,你可以直接阅读 project-layout 项目文档。
OneX 项目的目录结构也采用了 project-layout 的目录结构设计方式。OneX 项目一级目录结构如下:
设计合理的代码架构
设计合理的代码架构对 Go 项目的质量影响也很大。项目开发中可以采用的代码架构有很多,例如:六边形架构、洋葱架构、尖叫架构等。Go 项目开发中,用的最多的是简洁架构。简洁架构的架构设计跟 DDD 领域中的洋葱架构基本是保持一致的。
标准的简洁架构实现如下图所示:
我们可以看到图中有四层。 蓝色层、绿色层、红色层和黄色层。 每个圆圈代表软件的不同区域。 最外层是软件的最低级别,随着深入,级别会更高。 一般来说,当我们深入时,该层就不太容易发生变化。图中的黑色箭头表示依赖规则。
上述简洁架构,通过将系统的核心业务逻辑与外部环境和技术分离,实现了高内聚、低耦合的架构设计。它强调将业务规则和领域模型放在最核心的实体层和用例层中,以实现系统的可测试性、独立性、可维护性和可扩展性。同时,简洁架构也提供了清晰的分层结构,使开发团队能够更好地理解和管理系统的各个组成部分。
上图中的简洁架构分为了 4 层:
- 实体层(Entities):是指表示业务领域中的核心概念和业务规则的对象。一个实体可以是一个具有方法的对象,也可以是数据结构或方法的集合。其实,只要能够被业务应用使用,它可以是任何 形式。通常实体层是最不可能变化的一层。在 Go 项目开发中,数据库 Model 就可以看作是一个实体。实体层包含了领域模型的定义,它们具有独立的生命周期和行为,可以封装业务逻辑并保持数据的一致性。实体层通常包括实体(Entity)、值对象(Value Object)和聚合根(Aggregate Root)等。
- 用例层(Usecases):是指实现业务逻辑的部分,它负责协调各个实体和值对象之间的交互,以完成具体的业务需求。用例层,会被业务逻辑的变更而影响,但不会被其他层所影响,例如:实体层、UI、数据库等。用例层是领域模型的应用层,它将业务规则转化为可执行的操作,同时也负责处理与外部系统的交互。用例层通常包括用例(Usecase)和领域服务(Domain Service)等。
- 控制层(Controller):是指负责接收和处理外部请求的部分,它将用户的输入转化为领域模型的操作,并将结果返回给用户。控制层通常包括控制器(Controller)和网关(Gateway)等,它们负责处理请求的路由、验证和转发。在这个层次上还包括任何其他适配器,用于将数据从某个外部形式(如外部服务)转换为用例和实体使用的内部形式。
- 框架和驱动层(Framework & Driver):是指与外部环境和技术相关的部分,它提供了与外部系统的交互和数据存储的支持。框架和驱动层包括数据库、网络通信、Web 框架等组件和工具,它们与实体层、用例层和控制层进行交互,以实现系统的功能和性能要求。除了与下层通信相关的代码外,这一层,为了可维护性,我们不会编写太多的代码。
上述简洁架构的标准实现。但在我的开发的项目中,我更愿意采用以下软件架构设计方式:
- 模型层(Model):模型层在 Bob 大叔给出的简洁架构中也叫做实体层(Entities)在这一层中存储对象的结构和它的方法;
- 服务层(Service):服务层接收 HTTP/gRPC请求,并进行参数解析、参数校验、逻辑分发处理、请求返回这些操作。服务层会将逻辑分发给业务层,业务层处理后返回,返回数据在服务层中被整合再加工,最终返回给请求方。服务层相当于实现了业务路由的功能;
- 业务层 (Biz):业务层主要用来完成业务逻辑处理,我们可以把所有的业务逻辑处理代码放在业务层。业务层会处理来自服务层的请求,并根据需要请求存储层完成数据的 CURD 操作;
- 存储层(Store):存储层也即 Bob 大叔简洁架构中的 Frameworks & Drivers 层,负责与数据库、外部服务等的交互,作为应用程序的数据引擎进行应用数据的输入和输出。这里需要注意,存储层仅对数据库/外部服务执行 CRUD 操作,不封装任何业务逻辑。这一层也会起到数据转换的作用:将从数据库/微服务中获取的数据转换为服务层、业务层能识别的数据结构,将服务层、业务层的数据格式转换为数据库或外部服务能识别的数据格式。
可以看到想对于标准的简洁实现,我所以的简洁架构有了一些变化,变化点及原因如下:
- Entities -> Model:OneX 项目的 ORM 使用了 GORM,GORM 中跟底层数据库表映射的 Go 结构体成为 Model,为了保持一致,这里的 Entities 改名为 Model;
- Use Cases -> Biz:因为 Use Cases 不是很容易理解,所以这里改名为 Biz(Business);
- Service -> Controllers:因为 OneX 项目中,很多组件是一个 gRPC 服务,gRPC 中,通常用 Service 来指代 Controllers。又因为 OneX 使用了 Git 大仓模式,存储中还有 kubernetes controller 的实现,为了跟 kubernetes conotroller 进行区分。所以,为了跟 gRPC 的命名规范保持一致,并且跟 kubernetes controller 名字进行区分,这里将简洁架构中的 Controllers 层改名成了 Service 层;