首页
/ 基于 project-layout 的 Go 标准项目布局:目录规范、适用场景与源码级实践指南

基于 project-layout 的 Go 标准项目布局:目录规范、适用场景与源码级实践指南

2026-09-05 22:54:59作者:丁柯新Fawn

本文以本仓库的意大利语文档 README_it.md 为主体,系统讲解 Go 社区通用的标准项目布局(Standard Go Project Layout):每个核心目录(cmdinternalpkgapiwebconfigsscriptsdeployments 等)的职责、命名规则与取舍依据。读完之后,你将能够为自己的 Go 应用挑选合适的目录骨架、理解 internal 包的编译器级强制机制,并基于本仓库模板快速搭建一个结构清晰的 Go 项目。

一、这套布局的定位:社区模式集合,而非官方标准

文档开宗明义地强调:这套布局并不是 Go 核心团队定义的官方标准。它是 Go 生态中长期沉淀下来的一组常见历史与新兴项目布局模式,其中一些模式比其他模式更流行;同时它也附带了一些小改进,以及几乎所有足够大的真实世界应用都会用到的若干支撑目录。

文档还给出三个关键定位:

  • 刻意保持通用性:这套结构不试图强加某种特定的 Go 包结构(例如 Clean Architecture 之类的分层方式就不在此讨论范围内);
  • 社区协作成果:如果你发现新的模式,或者认为某个现有模式需要更新,应当通过 issue 提出;
  • 按需裁剪:目录"存在"不代表"必须使用"——连 vendor 模式也不是万能的。

什么时候该用、什么时候不该用

文档用加粗语气给出了一条最重要的建议:

如果你正在学习 Go,或者只是在开发 PoC(概念验证)或个人简单项目,这套布局是不必要的复杂化。请从真正简单的结构开始——一个 main.go 文件和 go.mod 就足够了。

随着项目演进,布局的重要性分阶段上升:

  1. 项目增长期:要时刻保证代码结构良好,否则会演变成"一堆隐藏依赖 + 全局状态"的混乱代码;
  2. 多人协作期:需要更有结构的布局,并引入管理包/库的通用方式;
  3. 开源或被依赖期:当你的仓库是开源项目、或有其他项目会 import 你的代码时,就必须明确私有包与代码(即 internal 目录)的边界。

文档给出的落地建议是:克隆本仓库,保留你需要的部分,删除其余一切。克隆命令如下(这是全文唯一涉及仓库地址的场景):

git clone https://gitcode.com/GitHub_Trending/pr/project-layout

Go Modules 前提:从 Go 1.14 起不再被 $GOPATH 束缚

从 Go 1.14 开始,Go Modules 正式达到生产可用。文档建议:除非你有特定理由不用,否则一律使用 Go Modules——这样你就不必再操心 $GOPATH 和项目放置位置的问题。

关于模块路径(module path)有一条容易被忽视的细节:

  • 仓库自带的 go.mod 文件内容非常简洁:
// go.mod (仓库根目录)
module github.com/YOUR-USER-OR-ORG-NAME/YOUR-REPO-NAME

go 1.19
  • 文档说明这份 go.mod 默认假设你的项目托管在 GitHub 上,但这并非强制要求——模块路径可以是任意值;
  • 不过模块路径的第一段应当包含一个点(域名形式)。当前版本的 Go 已不再强制这一点,但如果你使用稍旧版本的 Go,构建失败时不妨先怀疑这里(文档同时引用了 golang/go 的 issue #37554 与 #32819 供进一步阅读,见官方仓库)。

命名、格式与风格:先跑工具,再读指南

文档建议遇到命名、格式、风格问题时,先从运行 gofmt 开始。需要说明的是,意大利语文档中提到的标准 linter 是 golint,而仓库中最新的英文主文档 README.md 已更新为推荐 staticcheck——因为 golint 现已弃停维护;若以当前仓库状态为准,优先使用 staticcheck 这类仍在维护的 linter。

此外文档列出了一批必读的命名与风格指南(此处仅保留条目名称,不再附外部链接):

  • "Go Naming Conventions" 演讲(talks.golang.org,2014)
  • Effective Go 的 Naming 章节
  • 《Package names in Go》官方博客
  • CodeReviewComments wiki
  • rakyll/JBD 的《Go 包风格指南》(Package-Oriented Style)

以及关于包命名、包组织与代码结构建议的四场经典演讲(GopherCon EU 2018 Peter Bourgon 的工业级编程最佳实践、GopherCon Russia 2018 的 Go best practices、GopherCon 2017 Edward Muller 的 Go 反模式、GopherCon 2018 Kat Zien 的 Go 应用结构),和一篇关于"面向包的设计与架构分层"的中文文章。

二、Go 目录:cmdinternalpkgvendor

这是文档中权重最高的四个 Go 专属目录,也是整套布局的核心骨架。

/cmd:本项目的主应用程序

/cmd 存放本项目的主应用(可执行程序)。规则有四点:

  1. 每个应用的目录名应与期望的可执行文件名一致(例如 /cmd/myapp);
  2. 不要在应用目录里堆大量代码:如果代码可能被其他项目 import 复用,就放进 /pkg;如果不可复用、或你明确不希望别人复用,就放进 /internal——"你无法预测别人会怎么 import 你的代码,所以要把意图表达得足够显式";
  3. 最常见、也最推荐的做法是:写一个很小的 main 函数,只做 import 并调用 /internal/pkg 中的代码,别无其他;
  4. 子目录说明文档 cmd/README.md 列出了采用该模式的主流项目,包括 velero、moby、prometheus、influxdb、kubernetes、dapr、go-ethereum 等。

本仓库中 cmd/_your_app_/ 就是一个空占位目录,提示你按可执行文件名创建子目录。

/internal:编译器强制的私有代码

/internal 存放"不希望他人 import"的私有应用与库代码。文档强调两个机制性事实:

  • 该模式由 Go 编译器本身强制:只要包位于 internal 目录下,其他包就无法 import 它(除非共享公共祖先路径)。这是自 Go 1.4 起的行为(见 Go 1.4 发布说明中的 internal packages 一节);
  • internal 不限于顶层:你可以在项目树的任意层级放置多个 internal 目录。

文档还推荐了一个可选的二级结构,用于区分"共享的内部代码"与"非共享的内部代码"(小项目不必做,但能提供更清晰的包用途暗示):

  • 实际应用代码放在 /internal/app(例如 /internal/app/myapp);
  • 这些应用共享的内部代码放在 /internal/pkg(例如 /internal/pkg/myprivlib)。

这一推荐结构在本仓库中被真实落地为占位目录:

internal/
├── app/
│   └── _your_app_/        # 应用私有代码(不共享)
└── pkg/
    └── _your_private_lib_/  # 应用间共享的私有库

子目录文档 internal/README.md 进一步列出了使用 internal 的知名项目:hashicorp/terraform、influxdb、perkeep、jaeger、moby、minio 等,其中 hashicorp/waypoint 展示了 /internal/pkg 的用法。

/pkg:明确"可供外部使用"的公开库

/pkg 存放允许外部应用使用的库代码(例如 /pkg/mypubliclib)。文档对此目录的态度可以概括为三层:

  1. 谨慎放入:其他项目会 import 这些库并"默认它们能正常工作",因此放任何东西进去之前要想清楚;
  2. internal 才是硬保证:真正从机制上阻止 import 的是 internal 目录(Go 编译器强制),而 /pkg 的价值在于**显式地对外传达"这里的代码可以被安全使用"**这一契约。社区作者 Travis Jeffery 的博文《I'll take pkg over internal》对 pkginternal 的取舍给出了很好的综述(见子目录文档 pkg/README.md 的引用);
  3. 它也是工程组织手段:当项目根目录混杂大量非 Go 组件时,把 Go 代码统一收拢到 /pkg 下可以让各种 Go 工具更好用——这一点在 GopherCon EU 2018(Peter Bourgon)、GopherCon 2018(Kat Zien)与 GoLab 2018(Massimiliano Pippi)三场演讲中均有论述。

文档同时坦诚了社区争议:pkg 是一个常见但并非普遍接受的模式,Go 社区中有人并不推荐它;如果你的项目很小、多一层嵌套没有价值,完全可以不用。pkg/README.md 附有一长串使用 pkg 布局的知名仓库清单(containerd、istio、helm、k3s、kubernetes、moby、grafana、cockroach、etcd、linkerd、spire 等),可作为"采用与否"的参考样本。

最后,文档交代了 pkg 目录的历史起源:早期 Go 官方源码树曾用 pkg 目录存放其包,社区项目随之模仿了这一模式(Brad Fitzpatrick 的相关推文提供了更多背景)。

/vendor:应用依赖

/vendor 存放应用依赖——可以手工管理,也可以用你偏好的依赖管理工具,比如内置的 Go Modules:

  • 执行 go mod vendor 命令即可自动生成 /vendor 目录;
  • 注意:如果你没有使用 Go 1.14(该版本起默认启用 vendor 模式),可能需要在 go build 命令上追加 -mod=vendor 标志;
  • 构建库时不要提交你的应用依赖
  • 自 Go 1.13 起,Go 启用了 module proxy 特性(默认使用官方代理服务器 proxy.golang.org)。如果你的需求与约束能被它满足,就完全不需要 vendor 目录。

本仓库当前并未包含 vendor 目录,与"依赖交给模块代理/go mod vendor 生成"的定位一致。

三、服务应用目录:/api

/api 存放 OpenAPI/Swagger 规范、JSON schema 文件与协议定义文件。这是一个面向服务化应用的目录:接口契约与实现代码分离,便于生成客户端或文档。子目录文档 api/README.md 给出的参考项目是 kubernetes 与 moby 的 api 目录。

四、Web 应用目录:/web

/web 存放 Web 应用专属组件:静态 Web 资源、服务端模板与 SPA(单页应用)。从本仓库的实际结构看,该目录已被细分为三个占位子目录,给出了推荐的组织方式:

web/
├── app/        # 前端应用(如 SPA 源码)
├── static/     # 静态资源
└── template/   # 服务端模板

对应的目录说明见 web/README.md

五、通用应用目录:configs、init、scripts、build、deployments、test

/configs:配置模板与默认配置

存放配置文件模板或默认配置,例如 confdconsul-template 的模板文件也应放在这里。说明见 configs/README.md

/init:系统初始化与进程管理

存放系统初始化(systemd、upstart、sysv)与进程管理器/守护器(runit、supervisord)的配置。说明见 init/README.md

/scripts:构建与运维脚本

存放执行各类构建、安装、分析等操作的脚本。文档指出这类脚本的核心作用是让根级 Makefile 保持小巧、直接(以 hashicorp/terraform 的 Makefile 为范例)。

这一点在本仓库中被贯彻到了极致——仓库根目录的 Makefile 全文只有一行注释:

# note: call scripts from /scripts

即所有实际逻辑都被推给了 /scripts 下的脚本,根 Makefile 仅作为"入口提示"。这正示范了文档所说的"根 Makefile 小而简单"原则。更多示例见 scripts/README.md

/build:打包与持续集成

/build 承担打包(Packaging)与 CI 两类职责,文档建议拆成两个子目录:

  • /build/package:放置云镜像(AMI)、容器(Docker)、操作系统包(deb、rpm、pkg)的打包配置与脚本;
  • /build/ci:放置 CI(travis、circle、drone)的配置与脚本。文档特别提醒:某些 CI 工具(如 Travis CI)对配置文件位置要求非常严格,可以把配置放在 /build/ci 下,再通过链接(link)指到 CI 工具期望的路径(在可行的情况下)。

/deployments:部署配置与模板

存放 IaaS、PaaS、系统级以及容器编排的部署配置与模板:docker-compose、kubernetes/helm、mesos、terraform、bosh 等。文档注意到一个命名差异:在部分仓库中(尤其是用 kubernetes 部署的应用),这个目录叫 /deploy。本仓库采用了 deployments/ 命名,说明见 deployments/README.md

/test:外部测试应用与测试数据

/test 存放额外的外部测试应用与测试数据,内部结构可自由组织。文档给出两条与 Go 工具链直接相关的实用规则:

  • 大型项目建议设一个数据子目录,如 /test/data;若需要 Go 工具链忽略目录内容,应命名为 /test/testdata(Go 的构建/测试工具会自动跳过 testdata 目录);
  • 由于 Go 同样会忽略以 ._ 开头的目录和文件,测试数据目录的命名还有更大自由度——这也解释了本仓库为何大量使用 _your_app__your_private_lib_ 这类下划线前缀占位目录:它们既是模板占位符,又天然被 Go 工具忽略。

示例见 test/README.md

六、其他目录:docs、tools、examples、third_party、githooks、assets、website

目录 职责 补充说明
/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

值得注意的是 /tools 的 import 规则:工具既依赖项目代码(可 import pkg/internal),又不应被主程序反向依赖——这为"脚手架/代码生成器/内部 CLI"类工具划定了一个安全的存放位置。

七、不该出现的目录:/src

文档专设一节警告不要在 Go 项目中使用 /src 目录。理由分两层:

  1. 来源判断:Go 项目出现 src 目录,通常是因为开发者来自 Java 世界(Java 中 src 是常见模式)。文档直接建议:尽量别把 Go 项目做得像 Java 项目;
  2. 避免与 Go workspace 的 /src 混淆$GOPATH 环境变量指向(当前的)工作区(非 Windows 系统上默认是 $HOME/go),该工作区包含顶层的 /pkg/bin/src 三个目录;你的实际项目会落在工作区的 /src 之下。如果项目自身再嵌套一个 /src,路径就会变成 /some/path/to/workspace/src/your_project/src/your_code.go 这种双层 src。文档最后补充:虽然自 Go 1.11 起项目可以放在 GOPATH 之外,但这并不意味着 /src 布局就是好主意。

八、仓库骨架总览:一个可裁剪的纯模板

结合上文对文档各目录的解读,再看本仓库的实际骨架,可以完整对照出这套布局的全貌(仓库内不含任何 .go 源码文件,是一个纯目录模板,配合 LICENSE.md 与上文所示的 go.modMakefile):

.
├── api/                  # OpenAPI/Swagger/JSON schema/协议定义
├── assets/               # 图片、logo 等仓库资源
├── cmd/
│   └── _your_app_/       # 主应用(目录名=可执行文件名)
├── configs/              # 配置模板/默认配置(含 confd、consul-template)
├── deployments/          # IaaS/PaaS/编排部署(docker-compose、k8s/helm、terraform…)
├── docs/                 # 用户与设计文档
├── examples/             # 应用/公开库示例
├── githooks/             # Git hooks
├── init/                 # systemd/upstart/sysv、runit、supervisord
├── internal/
│   ├── app/_your_app_/      # 应用私有代码
│   └── pkg/_your_private_lib_/  # 应用间共享私有库
├── pkg/
│   └── _your_public_lib_/    # 可供外部 import 的公开库
├── scripts/              # 构建/安装/分析脚本(根 Makefile 保持精简)
├── test/                 # 外部测试应用与测试数据(testdata 可被 Go 忽略)
├── third_party/          # 外部工具、fork 代码
├── tools/                # 支撑工具(可 import pkg 与 internal)
├── web/
│   ├── app/              # SPA/前端应用
│   ├── static/           # 静态资源
│   └── template/         # 服务端模板
├── website/              # 项目网站数据(非 GitHub pages 时)
├── go.mod                # module 路径为占位符,需替换为你自己的
├── Makefile              # 仅一行:call scripts from /scripts
└── LICENSE.md

使用流程与文档的结论一致:克隆仓库 → 保留所需目录、删除其余 → 替换 go.mod 中的模块路径占位符(github.com/YOUR-USER-OR-ORG-NAME/YOUR-REPO-NAME)→ 把 cmdinternalpkg 下带下划线前缀的占位目录重命名为真实名称。下划线前缀目录同时享受 Go 工具链自动忽略的便利。

九、Badges:仓库文档面板

文档最后给出了 README badge 的推荐用法(此处按当前仓库内容说明用途,不附外链):

  • Go Report Card:会用 gofmtgo vetgocyclogolintineffassignlicensemisspell 扫描你的代码,把示例中的模块引用替换为你的项目即可;
  • Pkg.go.dev:Go 包发现与文档的新入口,可用其 badge 生成工具为项目创建 badge;
  • Release badge:展示项目的最新 release 版本号,把链接指向你的项目即可。

十、小结

这套布局的完整心法可以压缩为三句话:

  1. cmd 薄、internal 硬、pkg——可执行入口保持极小;私有性交给编译器强制的 internal;公开 API 才进 pkg 并视为对外承诺;
  2. 非 Go 组件各归其位——apiwebconfigsinitdeploymentsscriptstestdocswebsite 等目录让根目录始终可读;
  3. 按需裁剪——学习期只用 main.go + go.mod,项目长大、多人协作、对外发布三个阶段逐级加结构;目录在模板中存在,不等于你必须使用它。

文档末尾的 Notes 还提到:一个包含简单可复用配置、脚本与代码的"更有主见的标准项目模板"仍在进行中(WIP),可作为后续关注方向。

登录后查看全文
热门项目推荐
相关项目推荐