查看原文
其他

Go 项目标准布局?Go 官方出指南了

陈煎鱼 脑子进煎鱼了 2024-05-03

大家好,我是煎鱼。

所有的开发者写对应编程语言的项目时,总会涉及到一个纠结的问题,那就是这个项目怎么建?自己起的是否标准。希望找一个参考。

本文分两个部分:第一个部分是近期 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

一般最常见的就是基础的软件包,如果是单模块,包含多个文件。推荐的项目结构为:

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 与该目录名保持一致,例如:

package modname

// ... package code here

目录中的所有文件的包名都应当均为 modname。这些是通用的约定规则,下面其他类型也同理。

Basic command

Go 被用的最多的之一就是命令行工具,像是很多 k8s-client-go 都是用此编写。较大型的程序可以将其代码拆分为多个文件,所有文件都声明为 package main

这类命令行工具的安装方式为:

$ go install github.com/someuser/modname@latest

推荐的项目结构为:

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 目录代表该包是内部的,外部不可引用,意味着我们对内可以随意改变,不需要关注外部用户的引用。

推荐的项目结构为:

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

一个模块可以由多个包组成。我们会将多个包分成不同的目录,形成分层的结构。

推荐的项目结构为:

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

对应目录中子包的导入方式:

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。

推荐的项目结构为:

project-root-directory/
  go.mod
  internal/
    ... shared internal packages
  prog1/
    main.go
  prog2/
    main.go

安装的方式如下:

$ go install github.com/someuser/modname/prog1@latest
$ go install github.com/someuser/modname/prog2@latest

Packages and commands in the same repository

如果是命令行工具和软件包在同一个 GitHub 仓库中。

推荐的项目结构为:

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

Server project

这里主要关注用 Go 实现的部分,一个服务端项目一般不会有需要外部引用的包。都是一个独立的二进制文件进行运行和在服务器部署。

因此建议程序的程序、业务逻辑等均放在 internal 目录中,避免外部不恰当的引用。做出明确的区分。

在命令行程序方便,建议把 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

需要注意,如果存在和第三方共用的代码,应当及时抽离为单独的模块。例如: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 等这类如此模糊命名的软件库。

不管怎么说,能够达成局部共识,适合自己和团队项目规范的就是最好的。

推荐阅读


关注和加煎鱼微信,

一手消息和知识,拉你进技术交流群👇



你好,我是煎鱼,出版过 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路

日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,和大家交流!

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存