组织一个 Go 模块
原文地址:https://bbs.huaweicloud.com/blogs/440967
该文档翻译自 https://go.dev/doc/modules/layout
对于 Go 新手开发者而言,一个常见的问题是:我该如何组织我的 Go 项目,在文件和目录方面。该文当的目标是提供一个回答这些问题的向导。要充分利用该文当,请通过阅读 教程 和 管理模块源码 来确保您熟悉 Go 模块的相关基础知识。
Go 项目可以包括多个包,命令行程序,或者二者的结合。
1. Basic Package
一个基本的 Go 包将其所有的源代码放置于项目根目录。该项目由一个单一的模块组成,其中包括一个单一的包。包的名称与模块名称的最后一个路径组件匹配。对于一个非常简单的包而言,它只需要一个 Go 文件,该项目的结构如下:
project-root-directory/
go.mod
modname.go
modname_test.go
文件或包名可以是任意的。
假设该目录被上传到 Github 的 github.com/someone/modname
仓库中,go.mod
文件中的 module
行应当是 github.com/someone/modname
。
在 modname.go
文件中的源代码应当包含如下代码以声明包:
package modname
// ... 在这里书写包代码
然后,用户可以通过在 go
代码中通过类似如下的方式来导入这个包,依赖这个包:
import "github.com/someone/modname"
一个go包可以拆分为多个文件,它们都位于同一个目录中,例如:
project-root-directory/
go.mod
modname.go
modname_test.go
auth.go
auth_test.go
hash.go
hash_test.go
该目录中的所有文件都有 package modname
来声明包。
2. Basic Command
一个基本的可执行程序(或是命令行工具)是根据其复杂性和代码量来决定的。一个最简单的程序可以由一个单独的 Go 文件组成,其中定义了 func main
。更大的程序可以将代码分割为多个文件,所有文件都声明 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 的 github.com/someone/modname
仓库中,在 go.mod
文件中的 module
行应该是如下这样的:
module github.com/someone/modname
用户应当能够通过如下命令来安装这个包:
$ go install github.com/someone/modname@latest
3. 包含支持包的包或命令
更大的包或命令可能会得益于将其某些功能拆分到支持包中。最初,建议将此类功能包(支持包)放到名为 internal
的目录中;这样做 可以避免 其他(依赖这个模块的)模块依赖了我们不一定要公开的包(那些位于 internal
目录中的支持包),并且能够让这个模块被外部使用。因为其他项目无法从我们的 internal
目录中导入代码,我们可以自由地重构它的 API,并且通常在不破坏外部用户的情况下移动任何事情(我们可以任意的重构位于 internal
目录中的代码,这并不会破坏其他依赖这个模块的使用,因为那些外部的模块无法导入位于 internal
目录中的包)。因此,包的结构应当是这样的:
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
。
4. 多个包
一个模块可以由多个可导入的包组成;每个包都有它自己的目录,并且可以按层次结构化。这是一个示例项目结构:
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
hash_test.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"
trace
位于 internal/trace
中,它无法被外部的模块导入。建议尽可能将包保存在 internal
中。
5. 多个命令
位于同一仓库中的多个程序通常会有不同的目录:
project-root-directory/
go,mod
internal/
... 共享的内部包
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
目录中;虽然这在仅由命令组成的存储库中不是严格必要的,但这在同时包含命令和可导入包的混合仓库中非常有用,正如我们接下来要讨论的那样。
6. 包和命令在同一个仓库中
有时,一个仓库会都提供可导入包和可安装命令以及相关功能。如下是这样一个存储库的示例:
project-root-directory/
go.mod
modname.go
modname_test.go
auth/
auth.go
auth_test.go
internal/
... 共享的内部包
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/prog1@latest
$ go install github.com/someuser/modname/prog2@latest
7. 服务项目
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
...
... 该项目的其他目录包含非 Go 代码
如果服务器存储库中增加了对于其他项目共享的有用的包,最好将他们拆分到单独的模块中。