Go 工程化思考 0x1 : 为工程设计合理的目录结构
如果你尝试学习 Go 或者你正在为自己建立一个 PoC
或一个玩具项目,这个项目布局是没啥必要的。从一些非常简单的事情开始(一个 main.go 文件绰绰有余)。当有更多的人参与这个项目时,你将需要更多的结构,包括需要一个 toolkit 来方便生成项目的模板,尽可能大家统一的工程目录布局。
因为在大型多人协作的项目中如果没有统一的项目目录结构,随着项目不断的迭代,项目工程目录将变得凌乱无序,不易扩展,不可维护。合理设计目录结构的最终目的是为了提高项目的整洁可读,可扩展性和可交流性。
一个大家可以很好理解的架构本身就是开发人员的一种交流语言,本节将为大家介绍 Go 项目的标准目录布局,了解这些目录一方面是为了给自己设计工程目录结构的时候提供一个标准的参考,另外一方面在我们阅读 Go 项目源码的时候也可以很快了解想这些源码目录具体存放的是哪一方面的源代码。
同时 Go 语言特性支持的目录会重点进行讲解,以及在 GO 工程中那些目录是不推荐的。
一个基本的 Go 工程会有三个目录 cmd
, internal
, pkg
基础目录来分层,这并不是 Go 官方团队定义的标准,但这确实是目前 Go 生态中比较常见的布局形式,目前被普遍使用的目录结构是:github.com/golang-standards/project-layout
,我们可以在 github 上看到它的目录结构。
我们将代码克隆到本地后使用 tree 命令进行查看目录结构。
➜ WorkSpace tree go-project-layout
go-project-layout
├── LICENSE.md
├── Makefile
├── README.md
├── README_es.md
├── README_fr.md
├── README_ja.md
├── README_ko.md
├── README_ptBR.md
├── README_ro.md
├── README_ru.md
├── README_tr.md
├── README_zh-CN.md
├── README_zh-TW.md
├── README_zh.md
├── api
│ └── README.md
├── assets
│ └── README.md
├── build
│ ├── README.md
│ ├── ci
│ └── package
├── cmd
│ ├── README.md
│ └── _your_app_
├── configs
│ └── README.md
├── deployments
│ └── README.md
├── docs
│ └── README.md
├── examples
│ └── README.md
├── githooks
│ └── README.md
├── go.mod
├── init
│ └── README.md
├── internal
│ ├── README.md
│ ├── app
│ │ └── _your_app_
│ └── pkg
│ └── _your_private_lib_
├── pkg
│ ├── README.md
│ └── _your_public_lib_
├── scripts
│ └── README.md
├── test
│ └── README.md
├── third_party
│ └── README.md
├── tools
│ └── README.md
├── vendor
│ └── README.md
├── web
│ ├── README.md
│ ├── app
│ ├── static
│ └── template
└── website
└── README.md
一个 Go 项目主要包含前后端代码,构建后部署的工具,测试和文档这几部分,为了方便大家理解,可以将他们进行一下归类。
- 配置文件: 配置文件我们一般都会将其放在 configs 目录下
前端代码 :/web、/website、/assets
- web 目录存放前端代码,服务端模板和单页应用
- website 下存放 github页面或项目站点相关数据
- assets 存放项目中用的其他资源(Image,CSS,JavaScript等)
后端代码:/internal,/cmd,/vendor,/third_party,/pkg,/api
internal,主要是用来分离应用中共享和非共享的内部代码(go 1.4 开始编译的时候会进行强行校验)
限制公开程序实体只能被其父目录下面的包或者子包引用,适合多个服务共存的情况下隔离各个服务。
- 从运营管理服务相关代码和用户层代码隔离避免误调用。
- 目录辨识度高,有效杜绝使用者随意导入
cmd:程序入口代码,不含业务逻辑,有两个关键性的原则
- 不包含业务代码,不能被其他的包导入,负责程序的启动关闭以及配置的初始化等;
- cmd 下面的子目录名跟你期望生成的可执行程序的文件名应该是一致的。
- vendor:包含了工程中所有依赖的第三方源代码,是 go 中最早依赖包的管理方式,目前大多数项目是使用 go mod 的方式来进行管理,如果需要即使用以前的依赖,还要使用 go mod 我们可以通过
go mod vendor
命令来创建 vendor 目录,在 go 1.14 版本以后我们只需要使用go vendor
命令就可以了。 - third_party: 其中存放的也是第三方包,不过这些第三方包是我们自己进行了一定的修改,跟 vendor 进行区分,方便更新
- pkg:与 internal 相对,外部项目可以直接导入,在实际工作中,为了避免重复造轮子,我们会沉淀整个企业使用的公共包作为独立的仓库提供给各个应用程序使用。
- api:主要用来存储接口定义文件比如 proto 的一些定义文件。
项目工具,构建和部署相关:Makefile,/scripts/, /tools,/build, /deployments, /init
- Makefile:makefile提供了一些指令,方便我们编译我们的应用程序,通常工程的根目录下都会有这样的文件;
- scripts:脚本文件,完成构建,安装,分析检查等功能,Makefile 文件中各个指令的具体实现。
- tools:项目的一些脚本工具,比如根据项目模型生成相关的代码,可以调用 pkg 和 internal 下面的代码。
- build: 主要存放安装包和持续集成相关文件,比如说 Dockerfile。
- deployments:存放系统和容器编排部署配置和模板,比如docker-compose。
- init:应用程序初始化的脚本,比如systemd 和进程管理(比如supervisord)配置
- 外部测试代码和数据:/test,这里面用于存放外部包的测试代码和数据,比如经过魔改的第三方库,而应用里面的单元测试还是放在你应用相关的 go 文件下面。
文档:/docs,/examples, README.md,CHANGELOG
- docs:开发文档,用户手册,安装指南,设计文档等
- examples:存放工程内部和工程中可被外部导入的包的示例代码,方便使用者快速入门,企业内部公共包可以提供使用这些包构建一些内部项目地址
- README.md:一般用来介绍项目代码,功能和安装指南,文档地址等。
- CHANGELOG:记录每一次发版的变更内容
不建议的目录:/src,/model,/common,/util
- src: 在 GOPATH 模式下,代码会被放到 $GOPATH下面的 src 目录下,在导入路径中就会包含两个 src 目录,这样看上去不是很规范。
- model:不建议将实体或类型定义放到 model 目录里,特别是 MVC中 M 层的表结构定义,在 DDD 的设计思想下我们推荐按照业务领域来划分。
- utils 和 common :无法看出包的具体功能, 久而久之随着代码的迭代容易变成大杂烩。