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

大家好,我是煎鱼。

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

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

Go 图书系列

推荐阅读

相关推荐
不爱说话郭德纲15 小时前
聚焦 Go 语言框架,探索创新实践过程
go·编程语言
0x派大星2 天前
【Golang】——Gin 框架中的 API 请求处理与 JSON 数据绑定
开发语言·后端·golang·go·json·gin
IT书架2 天前
golang高频面试真题
面试·go
郝同学的测开笔记2 天前
云原生探索系列(十四):Go 语言panic、defer以及recover函数
后端·云原生·go
秋落风声3 天前
【滑动窗口入门篇】
java·算法·leetcode·go·哈希表
0x派大星4 天前
【Golang】——Gin 框架中的模板渲染详解
开发语言·后端·golang·go·gin
0x派大星5 天前
【Golang】——Gin 框架中的表单处理与数据绑定
开发语言·后端·golang·go·gin
三里清风_6 天前
如何使用Casbin设计后台权限管理系统
golang·go·casbin
0x派大星6 天前
【Goland】——Gin 框架中间件详解:从基础到实战
开发语言·后端·中间件·golang·go·gin
0x派大星6 天前
【Goland】——Gin 框架简介与安装
后端·golang·go·gin