文章目录
前言
作为一个新手,学习了golang中的http、net,也学习了gin、beego等web框架,也学习了zero-go这种脚手架。自己也在一些网站上学习了相关WEB
后端开发的代码,跟着敲了一段时间,换到一个新的教程又发现两者的写法是完全不同。所以我需要一种稳定的、适用于各种大中小项目的项目排版模式,来保证开发、维护这几个过程中的代码是容易阅读的。所以结合了各个版本规范,以及一些社区和公司的规范,整理一个版本供大家参考。
为什么要有目录规范?
在开发中大型项目时,往往会需要多人同时开发,并且这个开发周期能长达一个月多。这期间任何一位小伙伴提交的代码、存放的位置如果能按照目录规范来做,那么这个位置就包含了一定的信息,只要一看到这个代码位置,就知道了代码的大致功能,从而使整个团队协作效率提高,而且还能增加项目的可读性。同时大家使用不同的文件来表示各个功能可以防止出现代码冲突的问题。golang社区比较推荐的一个golang标准目录规范。虽然并不完全是go官方发布,但是其设计的合理性还是让很多大佬接受,所以我们这次也根据这种标准整理一个个人认为相对合理的目录分布。
Go Web开发下一种参考
Service-Controller-DAO 是一种常见的分层架构模式,它通过将系统的职责分解到不同的层次,使得每一层都专注于特定的功能。这种模式在Web应用程序开发中广泛使用,通常会有以下几个主要层次:
project-root/
|-- cmd/
| |-- main.go
|-- pkg/
| |-- somepackage/
| | |-- somepackage.go
| |-- anotherpackage/
| | |-- anotherpackage.go
|-- internal/
| |-- pkg/
| | |-- config/
| | | |-- config.go
| | |-- logger/
| | | |-- logger.go
| | |-- db/
| | | |-- db.go
| |-- biz/
| | |-- service/
| | | |-- service.go
| | |-- model/
| | | |-- model.go
| |-- constant/
| | |-- constants.go
| |-- entity/
| | |-- user.go
| | |-- order.go
| | |-- product.go
| |-- middleware/
| | |-- auth.go
| | |-- cors.go
| | |-- logging.go
| |-- router/
| | |-- router.go
| |-- controller/
| | |-- user_controller.go
| | |-- order_controller.go
| |-- service/
| | |-- user_service.go
| | |-- order_service.go
| |-- dao/
| | |-- user_dao.go
| | |-- order_dao.go
| |-- task/
| | |-- cron_task.go
|-- config/
| |-- development.yaml
| |-- production.yaml
| |-- staging.yaml
|-- go.mod
|-- go.sum
|-- README.md
/cmd
/cmd
表示当前项目的主干,一般来说是用来启动你所设计的服务。正如它的名字所示,其承担着启动任务的作用,每个应用程序的目录名应该与你想要的可执行文件的名称相匹配。
注意:通常来说,这个目录不会防止太多代码,一般来说只会有个小小的
main
函数作为程序的入口,并从其它目录导入和调用代码,除此之外没有别的东西。
/pkg
/pkg
目录中一般存放可以被外部应用使用的代码库,这里的代码可以被import
直接引用。所以,放在这个包里的代码一定要谨慎,不然其他项目引用时出现bug可就说不清了。
/internal
如果大家翻过Go的一些包的源码,就会发现这些包里会有的internal
文件夹,比如gin
标准包。
在Go语言项目中,/internal
文件夹通常用于存放那些仅对项目内部可见的包和相关的代码。这些包和文件对于项目的其他部分来说是私有的,它们不应该被外部项目引用。所以不难理解,当我们的web
项目只是要自己使用时,大多数服务相关的代码都是放到/internal
中。所以在/internal
文件夹中将会是整个项目的主要拆解。
/internal/pkg
一般来说我们会在/internal/pkg
中放置一些实例构建的相关代码,比如应用中其他部分要用到查表的gorm
、写日志要用到的zap
等等,在这里将定义配置结构体和加载配置的函数,以及创建实例的方法,每个文件单独创建对应的文件夹。如果可以的话,可以使用wire
包直接进行依赖注入的代码生成,可以省去要写实例创建的代码。
/internal/biz
biz
即business
的谐音,大家都这么缩写,于是就形成了一个约定了。因此在/internal/biz
中文件夹通常用于存放那些专注于业务逻辑的包,一般来说只有业务逻辑过于复杂的时候会额外增加这个包来更好的处理业务逻辑。这些包包含了项目的业务逻辑核心,通常包括服务层接口和服务实现、业务对象(Business Objects, BOs)、领域逻辑等。一般会放一些业务相关的方法,会构建对应的业务结构体,然后构建这个结构体包含哪些方法,这个结构体的工厂化创建方式…
biz
中的业务往往是复杂并且只专注于业务逻辑,要涉及到多个层,所以要放到这里,等等一些业务上多多少少要用到的,如果可以的话,这个结构也可以在往内嵌套一层,比如下面这样:
type xxxBiz struct {
logger *zap.Logger
db *gorm.db
}
func NewBiz(logger *zap.Logger, db *gorm.db ) *xxxBiz {
return &xxxBiz{
logger: logger,
db: *gorm.db,
}
}
我们需要一个logger
和一个db
。其中LogService
是后端服务中的操作,就是一些增删改查的这些会设计到后端数据的操作,接下来会详细讲解。
/internal/constant
这个比较容易理解,就是一些常量配置的集中地,方便后面程序配置的改动。
说到这里就不得不说代码编写中的一个习惯问题,不知道大家有没有听过”魔法数“这个东西,所谓魔法数,就是指程序中的某处突然有个数字或者字符串的判断,这个数字或字符串很难判断对上下文的关系,可能当时写的时候理解它的意思,但是时间久了,突然看到这段代码,可能还真的看不懂了。
func calculatePrice(quantity int) float64 {
// 这里 1.99就是魔法数
return float64(quantity) * 1.99
}
这些数值直接出现在源代码中,而没有相应的常量命名来说明其用途或意义,并且在后续的修改也会很困难。所以在团队协作或开发大项目的时候,我们尽可能的规范我们的常量代码。
/internal/entity
/internal/entity
文件夹通常用于存放那些表示业务实体的数据模型。这些实体模型是应用程序的核心组成部分,用于表示业务逻辑中的基本概念和对象,如用户、订单、产品等。这里主要是一些结构体,一般来说有3类:
- 数据库的字段对应
- 接口请求参数对应的结构体
- 接口返回参数对应的结构体
/internal/middleware
/internal/middleware 文件夹通常用于存放那些与HTTP中间件相关的代码。中间件是在HTTP请求处理过程中插入的一层逻辑,用于增强或修改请求和响应的处理流程。一些常用的中间件比如:
- 身份验证中间件: 用于验证请求中的身份验证信息,如JWT令牌验证。
- 权限检查中间件: 根据用户的权限决定是否允许请求继续。
- 日志记录中间件: 记录请求的信息,如请求方法、路径、客户端IP等。
- 错误处理中间件: 捕获处理过程中发生的错误,并进行统一处理或记录。
- 跨域资源共享(CORS)中间件: 设置适当的HTTP头部,允许跨域请求。
- 压缩中间件: 对响应进行压缩,以减少网络传输的开销。
- 限流中间件: 控制请求的频率,以防止资源被滥用。
- 请求/响应修饰中间件: 修改请求头或响应头,如设置安全相关的HTTP头部。
- 性能监控中间件: 记录请求处理的时间,以监控性能。
- 缓存中间件: 实现基于HTTP缓存的逻辑。
中间件根据业务的可能性也存在各种不同的中间件,中间件的存在就是为了让业务的访问控制和各类的请求流程能被处理。
/internal/router
/internal/router
文件夹通常用于存放与HTTP路由相关的代码。这部分代码负责定义HTTP请求的路由逻辑,即根据请求的URL和方法来分发请求到正确的处理函数。这个部分各类博客都一致认同,用来分配路由分组,将各类路径分配到对应的后端服务中。
/internal/controller
Contoller的主要职责是接收HTTP请求,调用Service层处理业务逻辑,并将处理结果返回给客户端。在这一层中并不需要关注那些业务逻辑,而是只需要获取http传送的参数,或则是进行一些关键参数的验证、以及用户鉴权的操作等等执行业务操作的前置操作。主要去调用Service层来执行这些业务逻辑,Controller将请求数据传递给Service层,并将Service层返回的结果进一步处理或者直接返回给客户端。
/internal/service
/internal/service
文件夹通常用于存放业务逻辑和服务层的代码。这些服务层代码通常实现了应用程序的核心业务逻辑,是处理业务需求的关键部分。这里容易和biz
的关系混淆,在此解释一下两者的区别。在service
中,主要是用来处理服务层的代码,主要负责实现业务逻辑的核心功能,比如处理用户请求、验证数据、调用数据访问层等,而在biz
中则是更侧重于业务逻辑的核心部分,通常包括业务对象、领域逻辑和一些复杂的业务规则等。
/internal/dao
/internal/data
文件夹通常用于存放那些与数据访问和存储相关的包。这些包主要涉及数据模型的定义、数据访问逻辑以及与数据库或其他数据存储系统的交互。这里会放置一些数据访问层的相关的逻辑如:
- 提供与数据库交互的接口实现
- 包括查询构造、数据读取、写入、更新和删除等操作。
/internal/task
如果你的业务中包含定时任务等操作,那么就可以将定时任务放到这个文件夹来做,相当于做一个整合,方便进行查找
/config
配置文件模板或默认配置。可能有各种环境的配置,都放在这个文件夹中
总结
对于上面的说到的部分,我们在此继续做个总结,用tree命令做一个演示图,方便大家直接参考。
project-root/
|-- cmd/
| |-- main.go
|-- pkg/
| |-- somepackage/
| | |-- somepackage.go
| |-- anotherpackage/
| | |-- anotherpackage.go
|-- internal/
| |-- pkg/
| | |-- config/
| | | |-- config.go
| | |-- logger/
| | | |-- logger.go
| | |-- db/
| | | |-- db.go
| |-- biz/
| | |-- service/
| | | |-- service.go
| | |-- model/
| | | |-- model.go
| |-- constant/
| | |-- constants.go
| |-- entity/
| | |-- user.go
| | |-- order.go
| | |-- product.go
| |-- middleware/
| | |-- auth.go
| | |-- cors.go
| | |-- logging.go
| |-- router/
| | |-- router.go
| |-- controller/
| | |-- user_controller.go
| | |-- order_controller.go
| |-- service/
| | |-- user_service.go
| | |-- order_service.go
| |-- dao/
| | |-- user_dao.go
| | |-- order_dao.go
| |-- task/
| | |-- cron_task.go
|-- config/
| |-- development.yaml
| |-- production.yaml
| |-- staging.yaml
|-- go.mod
|-- go.sum
|-- README.md
本文是经过个人查阅相关资料后理解的提炼,可能存在理论上理解偏差的问题,如果您在阅读过程中发现任何问题或有任何疑问,请不吝指出,我将非常感激并乐意与您讨论。谢谢您的阅读!