组织一个 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 modnameauth.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 代码

如果服务器存储库中增加了对于其他项目共享的有用的包,最好将他们拆分到单独的模块中。

posted @ 2024-11-30 00:58  Annlix  阅读(1)  评论(0编辑  收藏  举报