首页
/ Standard Go Project Layout 实战指南:Go 标准项目目录结构的完整设计与取舍

Standard Go Project Layout 实战指南:Go 标准项目目录结构的完整设计与取舍

2026-09-05 20:42:54作者:郜逊炳

本篇以 project-layout 仓库的韩语版主文档为核心,系统讲解"Standard Go Project Layout"这一社区公认的目录布局模式:每个目录(/cmd/internal/pkg/vendor/api/web/configs/init/scripts/build/deployments/test 等)的职责边界、适用场景与取舍理由,并结合仓库中真实的 go.modMakefile 与各目录 README 佐证。读完后你将能够判断哪些模式适合自己的项目、如何划分可公开与私有的包边界,以及如何避免诸如 /src 这类反模式。

定位:不是官方标准,而是社区共识

首先要明确一个前提:该布局不是 Go 核心开发团队定义的官方标准,而是 Go 生态中"历史沉淀 + 新兴"的项目布局模式的公共交集。其中一些模式比其他模式更流行(例如 internal 是 Go 编译器强制支持的,pkg 则见仁见智)。仓库自身也刻意保持"故意通用"(intentionally generic)的姿态——它只规定目录层面的组织方式,不强制任何具体的包内结构(比如不会替你去套 Clean Architecture)。

适用性判断是该文档着墨最多的部分:

  • 学 Go、做 PoC、做玩具项目时,这套布局是过度的(overkill)。从一个 main.go 文件(加上 go.mod)开始就足够了;
  • 项目成长后要保证代码结构良好,否则会退化成"大量隐藏依赖 + 全局状态"的烂泥代码;
  • 多人协作时更需要结构,此时是引入"统一管理包/库"的方式的时机;
  • 当你的项目是开源项目、或知道其他项目会 import 你仓库里的代码时,引入私有(internal)包就变得关键;
  • 官方建议是:Clone 仓库,只保留你需要的目录,删掉其余的——"存在不等于必须全用",没有任何模式在每个项目里都适用,连 vendor 模式都不是普遍性的。

在模块体系上,文档指出 Go 1.14 起 Go Modules 已具备生产就绪状态,除非有特定理由否则应当使用 Go Modules——这样就无需再为 $GOPATH 与项目存放位置操心。仓库根目录的 go.mod 给出了一个最小示例:

module github.com/YOUR-USER-OR-ORG-NAME/YOUR-REPO-NAME

go 1.19

可以看到:模块路径是一个占位符,说明该文件默认假设项目托管在 GitHub 上,但这并非硬性要求;模块路径可以是任意值,但第一个模块路径组件的名字里应当包含一个点(当前 Go 版本已不再强制,但使用较老版本 Go 时,没有点可能导致构建失败,不必惊讶)。

关于命名、格式化与风格,文档给出的起点是运行 gofmt 与 linter(韩语版文档写的是 golint;英文版主 README 已更新为:golint 已废弃不再维护,推荐使用持续维护的 staticcheck),并建议阅读 Go 官方风格指南(Go 2014 的命名演讲、Effective Go 的命名章节、包命名博客、Code Review Comments wiki、rakyll 的包风格指南等)。背景延伸阅读可参考 Medium 上的 Go Project Layout 长文,以及 GopherCon 系列演讲(Peter Bourgon 的工业级编程实践、Edward Muller 的 Go 反模式、Kat Zien 的"How Do You Structure Your Go Apps"等)。仓库同时维护了 17 种语言的译文(README.mdREADME_zh-TW.md 等),便于不同语言区的团队对齐理解。

Go 核心目录

/cmd:每个应用一个子目录,目录名即二进制名

/cmd 存放本项目的主应用,每个应用子目录的名字应当与你想要的可执行文件名一致(如 /cmd/myapp)。关键约束是:不要在应用目录里堆大量代码——

  • 如果代码可能被其他项目 import 使用,应放到 /pkg
  • 如果代码不可复用、或你不想让别人复用,应放到 /internal。文档原话是"别人会做出让你惊讶的事,所以把你的意图写明白!"
  • 最常见的形态就是一个"小 main 函数":只 import 并调用 /internal/pkg 里的代码,别无其他。

仓库中 cmd/README.md 给出了采用该模式的知名项目清单(velero、moby、prometheus、influxdb、kubernetes、dapr、go-ethereum 等),本仓库自身也以 cmd/_your_app_/ 占位目录演示了这一结构。

/internal:由 Go 编译器强制的私有边界

/internal 存放"不希望被别人 import"的私有应用与库代码。与 /pkg 最大的区别在于:internal 的可见性限制是由 Go 编译器本身强制执行的(该特性自 Go 1.4 引入),而不仅仅是约定。两个要点:

  1. internal 不局限于顶层——项目树的任意层级都可以有多个 internal 目录;
  2. 可选地,在 internal 内再做一层结构以区分"共享的内部代码"与"非共享的内部代码":实际应用代码放 /internal/app(如 /internal/app/myapp),这些应用共享的代码放 /internal/pkg(如 /internal/pkg/myprivlib)。小型项目不必强求,但留下这种"视觉线索"有助于表达包的预期用途。

仓库自身正是这样组织的:internal/app/_your_app_/internal/pkg/_your_private_lib_/ 两个占位目录(以下划线命名,Go 工具链会自动忽略)直接演示了这一推荐结构,internal/README.md 还列出了 terraform、influxdb、minio 等真实采用 internal 的项目,以及 /internal/pkg 的示例(hashicorp/waypoint)。

/pkg:显式承诺"对外可用"的库代码

/pkg 存放"外部应用可以放心使用"的库代码(如 /pkg/mypubliclib)。文档对此的态度很平衡:

  • 其他项目会假设这些库能正常工作而 import 它们,所以"往里放东西前要三思";
  • 严格来说 internal 才是保证私有包不可被 import 的更优解(编译器强制),/pkg 的价值在于显式沟通:这个目录里的代码是允许别人用的;
  • 另一个实用价值:当根目录塞满了大量非 Go 组件时,把 Go 代码聚拢到 /pkg 一处,便于运行各种 Go 工具;
  • 这是一个"常见但未被普遍接受"的模式:对每个使用它的知名仓库,都能找到十个不用的。小项目完全可以不用(多一层嵌套没有价值),等根目录变得拥挤(尤其非 Go 组件多)时再考虑。

仓库的 pkg/README.md 附有一份非常长的真实采用者清单——containerd、istio、gvisor、kubernetes、helm、etcd、cilium、argo-workflows 等上百个项目,可以作为模式流行度的直接证据。

/vendor:依赖是否入库的取舍

/vendor 存放应用依赖(手工管理或由依赖管理工具管理)。关键操作点:

  • go mod vendor 命令会自动生成 /vendor 目录;
  • 如果不是 Go 1.14(该版本起 -mod=vendor 默认开启),使用 go build 时可能需要手动加 -mod=vendor 标志
  • 如果你在做库(library),不要把应用依赖提交进仓库
  • 自 Go 1.13 起启用了模块代理(默认使用官方模块代理服务器),若该代理满足你的全部需求与约束(内网隔离、合规等场景除外),则可能完全不需要 vendor 目录

服务类与 Web 类应用目录

/api

存放 OpenAPI/Swagger 规范、JSON Schema 文件、协议定义文件(protobuf 等)。api/README.md 列举了 kubernetes、moby 等项目的 /api 用法作参照。

/web

Web 应用专属组件:静态 Web 资源、服务端渲染模板与 SPA。本仓库以 web/appweb/staticweb/template 三个子目录演示了典型的划分方式。

通用应用目录

/configs

配置文件模板或默认配置。confdconsul-template 的模板文件也应放在这里。

/init

系统 init(systemd、upstart、sysv)与进程管理器/守护器(runit、supervisord)的配置。

/scripts

执行构建、安装、静态分析等操作的脚本集合。其核心价值是让根目录 Makefile 保持小而简单。仓库的 Makefile 全文只有一行注释(# note: call scripts from /scripts),堪称这一理念的极端示范;scripts/README.md 引用了 kubernetes/helm、cockroach、terraform 的 scripts 目录作为范例。

/build

打包(Packaging)与持续集成(CI)两类内容:

  • 云镜像(AMI)、容器(Docker)、操作系统包(deb、rpm、pkg)的打包配置与脚本放 /build/package
  • CI 配置与脚本(travis、circle、drone)放 /build/ci。注意部分 CI 工具(如 Travis CI)对配置文件位置极为挑剔,应尽量把配置放到 /build/ci用链接(link)挂到 CI 工具期望的位置(可行时)。

/deployments

IaaS、PaaS、系统与容器编排的部署配置和模板(docker-compose、kubernetes/helm、mesos、terraform、bosh 等)。部分仓库(尤其是以 Kubernetes 部署的应用)习惯把该目录命名为 /deploy。仓库的 deployments/README.md 完整列出了这一目录的定位。

/test

存放额外的外部测试应用与测试数据(单元/集成测试的 *_test.go 仍与源码同包,这里指跨包的测试工程与数据)。文档给出的实操要点:

  • 结构自由,随意组织;大项目建议设数据子目录;
  • 若希望 Go 工具链忽略目录内容,可命名为 /test/data/test/testdata
  • Go 同时忽略以 ._ 开头的目录/文件,因此测试数据目录的命名自由度更大。

test/README.md 以 openshift/origin 的 test/testdata 为例。

其他辅助目录

目录 职责 仓库佐证
/docs 设计与用户文档(godoc 生成文档之外) docs/README.md
/tools 项目自身的辅助工具;这些工具可以 import /pkg/internal 的代码 tools/README.md
/examples 应用和/或公开库的使用示例 examples/README.md
/third_party 外部辅助工具、fork 的代码、第三方实用件(如 Swagger UI) third_party/README.md
/githooks Git hooks githooks/README.md
/assets 随仓库分发的素材(图片、logo 等) assets/README.md
/website 不使用 GitHub Pages 时,存放项目网站数据 website/README.md

每个目录都以"README.md + 真实项目示例清单"的方式落地,使得这套布局不只是纸面约定,而是可以逐一对照检查的目录树。

反模式:你不该有 /src

文档专门辟出一节警告 /src 反模式:

  • 部分 Go 项目出现 src 文件夹,通常是开发者从 Java 世界带来的习惯——你并不希望 Go 项目长得像 Java;
  • 不要与 $GOPATH 工作区里的 /src 混淆$GOPATH 指向当前工作区(非 Windows 系统默认 $HOME/go),该工作区顶层包含 /pkg/bin/src 三个目录,真实项目本就落在 /src 之下;
  • 若项目内部再建一层 /src,代码路径会变成 .../workspace/src/your_project/src/your_code.go 这种双重嵌套,毫无收益;
  • Go 1.11 起项目可以放在 GOPATH 之外,但这不代表项目内建 /src 是好主意。

质量徽章与后续计划

文档最后给出 README 徽章的推荐组合:

  • Go Report Card:会用 gofmtgo vetgocyclogolintineffassignlicensemisspell 扫描代码,把徽章中的项目引用替换为自己的项目即可;
  • GoDoc:提供 GoDoc 生成文档的在线版本(英文主文档中该项已加删除线,处于弃用状态);
  • Pkg.go.dev:Go 包检索与文档的新站点,可用其徽章生成工具制作徽章;
  • Release:展示项目最新 release 编号。

文档结尾还提到:一份包含示例/可复用配置、脚本与代码的"更有主张(more opinionated)"的项目模板当时仍在开发中——这提示该布局仓库的定位始终是"目录骨架的共识层",而非开箱即用的完整模板。

小结:如何落地这套布局

  1. 新项目从 main.go + go.mod 起步,模块路径首段带点(老版本 Go 的硬性要求);
  2. 出现第二个入口二进制时建 cmd/<binary>;代码开始被复用、且你主动想开放给外部时建 /pkg,想强制封闭时用 /internal(编译器兜底);
  3. 非 Go 资产按类型分流入 apiwebconfigsinitassetswebsite;构建/CI/部署资产分流入 scriptsbuilddeployments
  4. 依赖默认走 Go Modules 与模块代理,仅在需要离线/可复现构建时执行 go mod vendor 入库,做库则不提交依赖;
  5. Makefile + scripts/ 的组合保持构建入口精简,用 Go Report Card / Pkg.go.dev 徽章对外公示质量;
  6. 全程避免 /src,并记住:Clone 模板后"按需删减"是官方推荐用法。
登录后查看全文
热门项目推荐
相关项目推荐