前言
随着项目的代码量在不断地增长,不同的开发人员按自己意愿随意布局和创建目录结构,项目维护性就很差,代码也非常凌乱。良好的目录与文件结构十分重要,尤其是团队合作的时候,良好的目录与文件结构可以减少很多不必要的麻烦。项目目录结构规范的的本质是提高了代码的可读性,最终目的是提高团队协作效率,降低工程维护成本。
我们知道 java 项目结构是请求达到路由层控制器 controller,然后 controller 会去调用 service 层逻辑代码,然后 service 层会去调用 dao 层的接口方法,其实 dao 层都是以接口的形式提供,然后这些接口里头的都是操作数据库的方法,然后与 dao 层对应着的有一个 mapper,mapper 是以 xml 形式提供的,与 dao 层中的接口相对应,xml 中实际就是实现了 dao 接口中的这些具体方法,xml 中会与指定的 dao 接口中指定的方法进行绑定,xml 中会去写 sql 逻辑。具体请看架构师技能1:Java工程规范、浅析领域模型VO、DTO、DO、PO、优秀命名
一、Go语言自身项目的基本结构
首先我们先看Go语言自身项目的基本结构。
Go项目的项目结构自1.0版本发布以来一直十分稳定,直到现在Go项目的顶层结构基本没有大的改变。截至go项目commit 1e3ffb0c(2019.5.14),go项目结构如下:
$ tree -LF 1 ~/go/src/github.com/golang/go
./go
├── api/
├── AUTHORS
├── CONTRIBUTING.md
├── CONTRIBUTORS
├── doc/
├── favicon.ico
├── lib/
├── LICENSE
├── misc/
├── PATENTS
├── README.md
├── robots.txt
├── src/
└── test/
作为Go语言的“创世项目”,其项目结构的布局对后续的其他Go语言项目具有重要的参考意义,尤其是早期Go项目中src目录下面的结构,更是在后续被Go社区作为Go应用项目结构的模板被广泛使用。我们以早期的Go 1.3版本的src目录下的结构为例:
$ tree -LF 1 ./src
./src
├── all.bash*
├── all.bat
├── all.rc*
├── clean.bash*
├── clean.bat
├── clean.rc*
├── cmd/
├── lib9/
├── libbio/
├── liblink/
├── make.bash*
├── make.bat
├── Make.dist
├── make.rc*
├── nacltest.bash*
├── pkg/
├── race.bash*
├── race.bat
├── run.bash*
├── run.bat
├── run.rc*
└── sudo.bash*
src目录下面的结构三个特点:
1)代码构建的脚本源文件放在src下面的顶层目录下;
2)src下的二级目录cmd下面存放着go工具链相关的可执行文件(比如:go、gofmt等)的主目录以及它们的main包源文件;
3)src下的二级目录pkg下面存放着上面cmd下各工具链程序依赖的包、go运行时以及go标准库的源文件
在Go 1.3版本以后至今,Go项目下的src目录中发生了几次结构上的变动:
Go 1.4版本中删除了Go源码树中src/pkg/xxx中pkg这一层级目录而直接使用src/xxx;
Go 1.4版本在src下面增加internal目录,用于存放无法被外部导入仅Go项目自用的包;
Go 1.6版本在src下面增加vendor目录,但Go项目自身真正启用vendor机制是在Go 1.7版本中。vendor目录中存放了go项目自身对外部项目的依赖,主要是golang.org/x下的各个包,包括:net、text、crypto等。该目录下的包会在每次Go版本发布时做更新;
Go 1.13版本在src下面增加了go.mod和go.num,实现了go项目自身的go module迁移,go项目内所有包被放入名为std的module下面,其依赖的包依然是golang.org/x下的各个包
// Go 1.13版本go项目src下面的go.mod
module std
go 1.12
require (
golang.org/x/crypto v0.0.0-20200124225646-8b5121be2f68
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
golang.org/x/sys v0.0.0-20190529130038-5219a1e1c5f8 // indirect
golang.org/x/text v0.3.2 // indirect
)
下面是最新的Go 1.16版本src目录下的完整布局:
├── Make.dist
├── README.vendor
├── all.bash*
├── all.bat
├── all.rc*
├── bootstrap.bash*
├── buildall.bash*
├── clean.bash*
├── clean.bat
├── clean.rc*
├── cmd/
├── cmp.bash
├── go.mod
├── go.sum
├── internal/
├── make.bash*
├── make.bat
├── make.rc*
├── race.bash*
├── race.bat
├── run.bash*
├── run.bat
├── run.rc*
├── testdata/
...
└── vendor/
Go 1.14 Go Modules 投入使用后,就无需担心 $GOPATH 以及项目放置的位置。所以项目的 vendor 可以忽略了,建议直接使用 module 来管理依赖
二、优秀开源 的go项目结构样例
1、Docker
https://github.com/moby/moby
├── api // 存放对外公开的 API 规则
├── builder // 存放构建脚本等
├── cli // 命令行的主要逻辑
├── cmd // 存放可执行程序,main 包放这个目录中
├── contrib // 存放一些有用的脚本或文件,但不是项目的核心部分
├── docs // 存放文档
├── internal // 只在本项目使用的包(私有)
├── pkg // 本项目以及其他项目可以使用的包(公有)
├── plugin // 提供插件功能
2、Kubernetes
https://github.com/kubernetes/kubernetes
├── api
├── build // 存放构建脚本等
├── cmd
├── docs
├── pkg
├── plugin
├── test // 单元测试之外的测试程序、测试数据
├── third_party // 经过修改的第三方的代码
3、Gogs
https://github.com/gogs/gogs
├── cmd
├── conf // 对配置进行解析
├── docker // 存放 docker 脚本
├── models // MVC 中的 model
├── pkg
├── public // 静态公共资源,实际项目会将其存入 CDN
├── routes // 路由
├── scripts // 脚本文件
├── templates // 存放模板文件
总体上这些优秀开源项目,没有统一一致的目录结构方式,但大体上,有一些通用的地方,这就有
project-layout/README_zh.md at master · golang-standards/project-layout · GitHub做参考:
三、通用项目目录结构参考
参考:
project-layout/README_zh.md at master · golang-standards/project-layout · GitHub
1、web服务应用程序目录
/api
OpenAPI/Swagger 规范,JSON 模式文件,协议定义文件。
/web
特定于 Web 应用程序的组件:静态 Web 资产、服务器端模板和 SPAs。
2、通用应用目录
/configs
配置文件模板或默认配置。
将你的 confd
或 consul-template
模板文件放在这里。
/init
System init(systemd,upstart,sysv)和 process manager/supervisor(runit,supervisor)配置。
/scripts
执行各种构建、安装、分析等操作的脚本。
这些脚本保持了根级别的 Makefile 变得小而简单(例如, terraform/Makefile at main · hashicorp/terraform · GitHub )。
有关示例,请参见 /scripts 目录。
/build
打包和持续集成。
将你的云( AMI )、容器( Docker )、操作系统( deb、rpm、pkg )包配置和脚本放在 /build/package
目录下。
将你的 CI (travis、circle、drone)配置和脚本放在 /build/ci
目录中。请注意,有些 CI 工具(例如 Travis CI)对配置文件的位置非常挑剔。尝试将配置文件放在 /build/ci
目录中,将它们链接到 CI 工具期望它们的位置(如果可能的话)。
/deployments
IaaS、PaaS、系统和容器编排部署配置和模板(docker-compose、kubernetes/helm、mesos、terraform、bosh)。注意,在一些存储库中(特别是使用 kubernetes 部署的应用程序),这个目录被称为 /deploy
。
/test
额外的外部测试应用程序和测试数据。你可以随时根据需求构造 /test
目录。对于较大的项目,有一个数据子目录是有意义的。例如,你可以使用 /test/data
或 /test/testdata
(如果你需要忽略目录中的内容)。请注意,Go 还会忽略以“.”或“_”开头的目录或文件,因此在如何命名测试数据目录方面有更大的灵活性。
有关示例,请参见 /test 目录。
我们可以参考project-layout/README_zh.md at master · golang-standards/project-layout · GitHub
3、其他目录
/docs
设计和用户文档(除了 godoc 生成的文档之外)。
有关示例,请参阅 /docs 目录。
/tools
这个项目的支持工具。注意,这些工具可以从 /pkg
和 /internal
目录导入代码。
有关示例,请参见 /tools 目录。
/examples
你的应用程序和/或公共库的示例。
有关示例,请参见 /examples 目录。
/third_party
外部辅助工具,分叉代码和其他第三方工具(例如 Swagger UI)。
/githooks
Git hooks。
/assets
与存储库一起使用的其他资产(图像、徽标等)。
/website
如果你不使用 Github 页面,则在这里放置项目的网站数据。
有关示例,请参见 /website 目录。
4、不应该拥有的目录
/src
有些 Go 项目确实有一个 src
文件夹,但这通常发生在开发人员有 Java 背景,在那里它是一种常见的模式。如果可以的话,尽量不要采用这种 Java 模式。你真的不希望你的 Go 代码或 Go 项目看起来像 Java:-)
不要将项目级别 src
目录与 Go 用于其工作空间的 src
目录(如 How to Write Go Code 中所述)混淆。$GOPATH
环境变量指向你的(当前)工作空间(默认情况下,它指向非 windows 系统上的 $HOME/go
)。这个工作空间包括顶层 /pkg
, /bin
和 /src
目录。你的实际项目最终是 /src
下的一个子目录,因此,如果你的项目中有 /src
目录,那么项目路径将是这样的: /some/path/to/workspace/src/your_project/src/your_code.go
。注意,在 Go 1.11 中,可以将项目放在 GOPATH
之外,但这并不意味着使用这种布局模式是一个好主意。
四、代码架构
我们在《架构设计1:谈谈架构》已经提到代码架构,主要定义内容:
一、代码单元:
1、配置设计
2、框架、类库。
二、代码单元组织:
1、编码规范,编码的惯例。
2、项目模块划分
3、顶层文件结构设计,比如mvc设计。
4、依赖关系
我们再次只提分层依赖和目录规范结构:
1、代码分层和依赖
代码分层,让不同层次的代码做不同的动作。层次清晰的代码,提高可读性,从代码结构就大概能了解到代码是如何分层,每层大概功能是什么。例如java常用的Controller、Service、Mapper/Dao三层代码结构,其各层的代码逻辑范围。
2、默认上层依赖于下层
以来规则规定上层的代码可以依赖下层,但是下层的代码不可以依赖上层。也就是说下层逻辑不可以依赖任何上层定义的变量,函数,结构体,类,模块等等代码实体。
通过分层,一个庞大系统切分成不同部分,便于分工合作和维护。
应用层:主要负责具体的业务逻辑处理
服务层:提供可复用的服务
数据层:负责数据的存储和访问
假如说,最上层应用层处使用了 go 语言的 gorm 三方库,并定义了 gorm 相关的数据库结构体及其 tag 等。那么下层数据层不可以引用任何外层中 gorm 相关的结构体或方法,甚至不应该感知到 gorm 的存在。
但是,分层架构也有一些挑战:①必须合理规划层次边界和接口;②禁止跨层次的调用及逆向用。 《架构设计5:架构模式-分层模式》 《架构设计5:架构模式-分层模式》 《架构设计5:架构模式-分层模式》
3、参考的项目目录结构
├── app/application // App层,处理Adapter层适配过后与框架、协议等无关的业务逻辑
├ ├── api 处理OpenAPI 接口请求
├ ├── web 请求Web页面请求
├ ├── consumer //(可选)处理外部消息
├ ├── scheduler/task //处理定时任务,比如Cron格式的定时Job
├── domain // Domain层,最纯粹的业务实体及其规则的抽象定义
│ ├── interface/gateway // 领域网关,model的核心逻辑以Interface形式在此定义,交由biz层去实现
│ └── model // 领域模型实体
├── biz/module 业务/业务模块层
├ └──module1
├ ├──dao 数据库层
├ ├──model 业务模型
├ ├──entity 数据库模型
├ ├──service 业务逻辑
├ └──manager 复用逻辑
├── configs 配置
├── init //系统初始化
├── pkg
├── public // 静态公共资源,实际项目会将其存入 CDN
├── build 打包和持续集成
├── scripts // 脚本文件:执行各种构建、安装、分析等操作的脚本
├── test // 单元测试之外的测试程序、测试数据
├── plugin 各种插件
├── util/tools 工具包
├── main.go 项目运行入口
└── pkg // 各层可共享的公共组件代码