大家好,我是煎鱼。
所有的开发者写对应编程语言的项目时,总会涉及到一个纠结的问题,那就是这个项目怎么建?自己起的是否标准。希望找一个参考。
本文分两个部分:第一个部分是近期 Go 官网输出的 "Organizing a Go module" 的资料,具有官方指导意义。第二个部分社区的 golang-standards,存在了相当长的时间,较为知名。
官方版本的模块布局
一个 Go 项目一般包含软件包(package)、命令行程序(command)或两者(package+command)的组合。这些是 Go 这门编程语言的基本组成单元。
本指南按项目类型编排,以下均为官方示例,希望大家都能在此找到自己所需要的项目布局指引。
以下是涉及到的类型目录:
- Basic package
- Basic command
- Package or command with supporting packages
- Multiple packages
- Multiple commands
- Packages and commands in the same repository
- Server project
Basic package
一般最常见的就是基础的软件包,如果是单模块,包含多个文件。推荐的项目结构为:
go
project-root-directory/
go.mod
modname.go
modname_test.go
auth.go
auth_test.go
hash.go
hash_test.go
可能会有同学问,go module path 按什么规则命名?以上述目录为例。
假设其上传到 github.com/someuser/modname
的 GitHub 仓库。则 go mod 中的 module 行应当是 github.com/someuser/modname
。推荐的标准是 module path 和 repo path 要保持一致。
推荐 module name 与该目录名保持一致,例如:
go
package modname
// ... package code here
目录中的所有文件的包名都应当均为 modname。这些是通用的约定规则,下面其他类型也同理。
Basic command
Go 被用的最多的之一就是命令行工具,像是很多 k8s-client-go 都是用此编写。较大型的程序可以将其代码拆分为多个文件,所有文件都声明为 package main
。
这类命令行工具的安装方式为:
ruby
$ go install github.com/someuser/modname@latest
推荐的项目结构为:
go
project-root-directory/
go.mod
auth.go
auth_test.go
client.go
main.go
一般入口文件 main.go 文件中会包含 func main
,这是一个 Go 编程中的约定。在程序中,入口文件也可以叫 modname.go 或其他任何名称。但这是在多入口的情况下比较多见。
Package or command with supporting packages
如果存在较大型的包和命令行工具,一般推荐将某些功能拆分为支持包。也就是将功能类包放入 internal 目录中。
internal 目录代表该包是内部的,外部不可引用,意味着我们可以随意改变,不需要关注外部用户。
推荐的项目结构为:
go
project-root-directory/
internal/
auth/
auth.go
auth_test.go
hash/
hash.go
hash_test.go
go.mod
modname.go
modname_test.go
modname.go 文件声明为 package modname
,auth.go 文件声明为 package auth
等。由于是在同个模块,因此 modname.go 可以导入internal 目录下的 auth 包。
Multiple packages
一个模块可以由多个包组成。我们会将多个包分成不同的目录,形成分层的结构。
推荐的项目结构为:
go
project-root-directory/
go.mod
modname.go
modname_test.go
auth/
auth.go
auth_test.go
token/
token.go
token_test.go
hash/
hash.go
internal/
trace/
trace.go
go.mod 中的 module 行为:
arduino
module github.com/someuser/modname
对应目录中子包的导入方式:
arduino
import "github.com/someuser/modname"
import "github.com/someuser/modname/auth"
import "github.com/someuser/modname/auth/token"
import "github.com/someuser/modname/hash"
结合前面小节的内部包功能,internal/trace 仅在本模块使用。不会被外部的第三方所引用。
Multiple commands
如果在一个 GitHub 仓库中存在多个命令行程序。一般会推荐拆分不同的项目目录。每个子命令行程序会有自己的 main.go。
推荐的项目结构为:
go
project-root-directory/
go.mod
internal/
... shared internal packages
prog1/
main.go
prog2/
main.go
安装的方式如下:
ruby
$ go install github.com/someuser/modname/prog1@latest
$ go install github.com/someuser/modname/prog2@latest
Packages and commands in the same repository
如果是命令行工具和软件包在同一个 GitHub 仓库中。
推荐的项目结构为:
go
project-root-directory/
go.mod
modname.go
modname_test.go
auth/
auth.go
auth_test.go
internal/
... internal packages
cmd/
prog1/
main.go
prog2/
main.go
根据项目内不同的功能属性做了分层的结构切分。假设该模块名为 github.com/someuser/modname
。
用户如果想用软件包,可以直接导入:
arduino
import "github.com/someuser/modname"
import "github.com/someuser/modname/auth"
想用命令行工具,可以使用如下命令进行安装:
ruby
$ go install github.com/someuser/modname/cmd/prog1@latest
$ go install github.com/someuser/modname/cmd/prog2@latest
Server project
这里主要关注用 Go 实现的部分,一个服务端项目一般不会有需要外部引用的包。都是一个独立的二进制文件进行运行和在服务器部署。
因此建议程序的程序、业务逻辑等均放在 internal 目录中,避免外部不恰当的引用。做出明确的区分。
在命令行程序方便,建议把 go 相关命令放在 cmd 目录下,做出明确的区分。
推荐的项目结构为:
erlang
project-root-directory/
go.mod
internal/
auth/
...
metrics/
...
model/
...
cmd/
api-server/
main.go
metrics-analyzer/
main.go
...
... the project's other directories with non-Go code
需要注意,如果存在和第三方共用的代码,应当及时抽离为单独的模块。例如:xxx-common,避免循环依赖。
社区版本 golang-standards/project-layout
在 golang-standards/project-layout
项目中,其自称是 Go 项目标准布局。(仓库名):
- /cmd:项目主要的应用程序。
- /internal:私有的应用程序代码库,这些是不希望被其他人导入的代码。
- 应用程序实际的代码可以放在 /internal/app 目录(如:internal/app/myapp)。
- 应用程序的共享代码放在 /internal/pkg 目录(如:internal/pkg/myprivlib)中。
- /pkg:外部应用程序可以使用的库代码(如:/pkg/mypubliclib)。其他项目将会导入这些库来保证项目可以正常运行。
- /vendor:应用程序的依赖关系,可通过执行 go mod vendor 执行得到。
- /configs:配置文件模板或默认配置。
- /init:系统初始化(systemd、upstart、sysv)和进程管理(runit、supervisord)配置。
- /scripts::用于执行各种构建,安装,分析等操作的脚本。
更具体的布局介绍,大家可以参见 project-layout 项目的 README,内容比较长,其基本把方方面面的目录都考虑到了(人多力量大)。
需要注意,前两年,Go 官方团队已经声明其不代表 Go 官方标准,是一份开源社区方面的资料。仅供参考。
总结
今天我们结合官方推荐的布局方式和社区实现的 Go 项目标准布局进行了一番说明和演示。你会发现一些地方是较为通用的,例如:internal、cmd 的分层目录。
在约定俗成的内容上,module path 和 package name 和入口文件 main.go 的命名。虽然没有工具强制约束,但够给大家带来较好的可读性。
这些都是非常不错的。
两者间比较不一样的是:对于是否要有 pkg 目录这一存在。社区和官方存在一定的争议。rsc 明确表达过,不应该存在 pkg、util 等这类如此模糊命名的软件库。
不管怎么说,能够达成局部共识,适合自己和团队项目规范的就是最好的。
文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blo... 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。