组织 Go 模块
对于 Go 新手开发人员来说,一个常见的问题是“如何组织我的 Go 项目?”,这是指文件和文件夹的布局。本文档的目的是提供一些指导原则,帮助回答这个问题。为了充分利用本文档,请确保您已通过阅读教程和管理模块源熟悉 Go 模块的基础知识。
Go 项目可以包括软件包、命令行程序或两者结合。本指南按项目类型进行组织。
基本软件包
基本 Go 软件包在其代码都位于项目的根目录中。该项目由一个模块组成,该模块由一个软件包组成。软件包名称与模块名称的最后一个路径组件相匹配。对于只需要一个 Go 文件的非常简单的软件包,项目结构如下
project-root-directory/
go.mod
modname.go
modname_test.go
[在本文档中,文件/软件包名称完全是任意的]
假设此目录已上传到 github.com/someuser/modname
的 GitHub 存储库,则 go.mod
文件中的 module
行应显示 module github.com/someuser/modname
。
modname.go
中的代码通过以下方式声明包
package modname
// ... package code here
然后,用户可以通过在 Go 代码中使用以下方式 import
该包来依赖该包
import "github.com/someuser/modname"
Go 包可以拆分为多个文件,所有文件都位于同一目录中,例如
project-root-directory/
go.mod
modname.go
modname_test.go
auth.go
auth_test.go
hash.go
hash_test.go
目录中的所有文件都声明 package modname
。
基本命令
基本可执行程序(或命令行工具)的结构根据其复杂性和代码大小而定。最简单的程序可以由定义了 func main
的单个 Go 文件组成。较大的程序可以将其代码拆分为多个文件,所有文件都声明 package main
project-root-directory/
go.mod
auth.go
auth_test.go
client.go
main.go
此处 main.go
文件包含 func main
,但这只是一个约定。还可以将“main”文件称为 modname.go
(对于 modname
的适当值)或其他任何名称。
假设此目录已上传到 github.com/someuser/modname
的 GitHub 存储库,则 go.mod
文件中的 module
行应显示
module github.com/someuser/modname
并且用户应该能够使用以下方式在其计算机上安装它
$ go install github.com/someuser/modname@latest
带有支持包的包或命令
较大的包或命令可能会受益于将某些功能拆分为支持包。最初,建议将此类包放置在名为 internal
的目录中; 这会阻止 其他模块依赖于我们不一定希望公开和支持外部使用的包。由于其他项目无法从我们的 internal
目录导入代码,因此我们可以自由地重构其 API,并在不破坏外部用户的情况下四处移动内容。包的项目结构如下
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
可以按如下方式导入 auth
包
import "github.com/someuser/modname/internal/auth"
在 internal
目录中带有支持包的命令的布局非常相似,不同之处在于根目录中的文件声明 package main
。
多个包
一个模块可以包含多个可导入的包;每个包都有自己的目录,并且可以按层次结构组织。以下是一个示例项目结构
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
行显示
module github.com/someuser/modname
modname
包位于根目录中,声明 package modname
,用户可以使用以下方式导入
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
中的包 trace
无法在该模块外部导入。建议尽可能将包保留在 internal
中。
多个命令
同一存储库中的多个程序通常具有单独的目录
project-root-directory/
go.mod
internal/
... shared internal packages
prog1/
main.go
prog2/
main.go
在每个目录中,程序的 Go 文件声明 package main
。顶级 internal
目录可以包含存储库中所有命令使用的共享包。
用户可以使用以下方式安装这些程序
$ go install github.com/someuser/modname/prog1@latest
$ go install github.com/someuser/modname/prog2@latest
一种常见约定是将存储库中的所有命令都放在 cmd
目录中;虽然在仅包含命令的存储库中这不是严格必需的,但它在同时具有命令和可导入包的混合存储库中非常有用,我们将在下面讨论。
同一存储库中的包和命令
有时,存储库会提供可导入的包和具有相关功能的可安装命令。以下是此类存储库的示例项目结构
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
,用户现在可以同时从中导入包
import "github.com/someuser/modname"
import "github.com/someuser/modname/auth"
并从中安装程序
$ go install github.com/someuser/modname/cmd/prog1@latest
$ go install github.com/someuser/modname/cmd/prog2@latest
服务器项目
Go 是实现服务器的常用语言选择。此类项目的结构差异很大,因为服务器开发涉及很多方面:协议(REST?gRPC?)、部署、前端文件、容器化、脚本等。我们将重点介绍此处用 Go 编写的项目部分。
服务器项目通常不会有用于导出的包,因为服务器通常是自包含的二进制文件(或一组二进制文件)。因此,建议将实现服务器逻辑的 Go 包保留在 internal
目录中。此外,由于项目可能还有许多其他包含非 Go 文件的目录,因此最好将所有 Go 命令都保存在 cmd
目录中
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
如果服务器存储库不断增长,并且包变得对与其他项目共享有用,则最好将它们拆分为单独的模块。