IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    煎鱼发表于 2023-10-20 12:36:18
    love 0

    大家好,我是煎鱼。

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

    本文分两个部分:第一个部分是近期 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 等这类如此模糊命名的软件库。

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

    文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

    Go 图书系列

    • Go 语言入门系列:初探 Go 项目实战
    • Go 语言编程之旅:深入用 Go 做项目
    • Go 语言设计哲学:了解 Go 的为什么和设计思考
    • Go 语言进阶之旅:进一步深入 Go 源码

    推荐阅读

    • Go 标准库想增加 metrics 指标,你支持吗?
    • 互联网公司裁员的预兆和手段
    • Go1.21 那些事:泛型库、for 语义变更、统一 log/slog、WASI 等新特性,你知道多少?


沪ICP备19023445号-2号
友情链接