首页
/ Go 项目目录结构设计标准:深入解析 project-layout 的 Standard Go Project Layout

Go 项目目录结构设计标准:深入解析 project-layout 的 Standard Go Project Layout

2026-09-06 21:15:05作者:谭伦延

本文基于 golang-standards/project-layout 仓库的核心文档 README.md,系统讲解 Go 生态中最通用的项目目录布局:从 /cmd/internal/pkg 三大核心 Go 目录的取舍,到 /api/web/deployments 等应用级目录的分工,并结合仓库内 go.modMakefile 与各目录说明文件,给出可直接落地到真实项目的结构方案与反模式规避指南。

一、定位与适用边界:它不是官方标准,而是一套社区共识

理解这套布局之前,必须先明确它的三个定位(均来自 README.md 的 Overview 章节):

  1. Basic(基础级):它只关注"项目长什么样",不关注目录里写什么。比如它不覆盖 Clean Architecture 等更细粒度的架构分层,这类更深入的架构选择需要另外设计。
  2. 非官方标准:这不是 Go 核心团队定义的官方标准,而是 Go 生态中"历史上存在且正在涌现"的目录模式集合。其中一些模式比另一些更流行,但没有任何一个模式适用于所有项目——连 /vendor 模式也不是普遍适用的。
  3. 故意保持通用:该布局刻意不强制任何特定的 Go 包结构,属于社区共建成果,README 也明确邀请开发者提交 Issue 来补充新模式或修正旧模式。

什么时候不该用它

README 中有一段加粗的关键提示,值得原样继承:

如果你在学 Go,或者只是在做一个 PoC / 简单项目,这套布局是过度设计。从一个单独的 main.go 文件加一个 go.mod 开始就够了。

正确的演进路径是:项目长大后,保证代码结构良好(否则你会得到一堆隐藏依赖和全局状态的混乱代码);多人协作时,引入统一的包管理方式;当项目开源、或有其他项目 import 你的代码时,才需要认真考虑 /internal 私有包机制。

官方 Go 团队在 Organizing a Go module 文档中也提供了组织 Go 模块的通用准则,包含 internalcmd 两类目录模式,与本布局高度一致。

仓库自身的 go.mod:模块路径的取值约束

仓库根目录的 go.mod 是一个最小可用的模板:

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

go 1.19

对照 README 的说明,这里有两个实操要点:

  • 基本 go.mod 假设项目托管在 GitHub 上,但这不是强制要求——模块路径可以是任意值;
  • 模块路径的第一段通常需要包含点号(如 github.com/...)。当前版本的 Go 已不再强制这一点,但如果使用稍旧的 Go 版本,缺少点号可能导致构建失败,遇到此类报错应检查模块路径格式。

Go 1.14 起 Go Modules 已可用于生产环境:除非有特定原因,应优先使用 Go Modules,这样就不必再关心 $GOPATH 与项目放置位置的问题。

二、目录总览

下表汇总了 README.md 中定义的全部目录及其职责,仓库内对应目录大多已创建并附带说明文件:

目录 职责 仓库内说明文件
/cmd 可执行主程序入口,目录名即二进制名 cmd/README.md
/internal 私有应用与库代码(编译器强制不可外部 import) internal/README.md
/pkg 可供外部项目 import 的公开库代码 pkg/README.md
/api OpenAPI/Swagger 规格、JSON Schema、协议定义文件 api/README.md
/web 静态资源、服务端模板、SPA 等 Web 组件 web/README.md
/configs 配置文件模板与默认配置 configs/README.md
/init systemd/upstart/sysv 与进程管理器配置 init/README.md
/scripts 构建、安装、分析等脚本 scripts/README.md
/build 打包与 CI 配置(文档定义,仓库模板未包含)
/deployments IaaS/PaaS/容器编排部署配置(docker-compose、k8s/helm、terraform) deployments/README.md
/test 额外的外部测试应用与测试数据 test/README.md
/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

此外还有一个不应出现的反模式目录:/src,后文专门讨论。

三、核心 Go 目录详解

3.1 /cmd:每个可执行程序一个子目录

规则(见 cmd/README.md):

  • 项目的所有主应用放在 /cmd 下,每个应用的子目录名必须等于你想要的可执行文件名(例如 /cmd/myapp 构建出 myapp);
  • 应用目录里不要堆大量代码。判断标准很直接:这段代码能否被其他项目 import 复用?
    • 能 → 放到 /pkg
    • 不能复用、或不希望被复用 → 放到 /internal
    • README 特别强调"要显式表达你的意图"——别人会做出你想不到的事情;
  • 典型的 main 函数应当很小:只 import 并调用 /internal/pkg 中的代码,不包含其他逻辑。

仓库模板中已预留 cmd/_your_app_/ 占位目录(下划线前缀会让 Go 工具忽略该目录,避免占位内容干扰构建)。

3.2 /internal:由 Go 编译器强制执行的私有包

这是整套布局中唯一由语言机制兜底的目录模式(见 internal/README.md):

  • /internal 下的代码不允许项目外的代码 import,这一约束由 Go 编译器本身强制执行(该模式自 Go 1.4 引入),不依赖团队纪律;

  • internal 不需要只在顶层出现——项目树的任何层级都可以有多个 internal 目录,粒度完全由你自己控制;

  • 可选的二级结构用于区分"应用专属代码"与"应用间共享代码":

    • /internal/app/<应用名>:某个应用自身的代码(如 /internal/app/myapp);
    • /internal/pkg/<库名>:多个应用共享的内部库(如 /internal/pkg/myprivlib)。

    仓库模板中对应的 internal/app/_your_app_/internal/pkg/_your_private_lib_/ 两个占位目录正是这一推荐结构;

  • 这套二级结构不是必须的,小项目不必引入,但它提供了"代码用途如何"的视觉线索。

判断一段代码该不该进 internal 的核心问题是:你是否希望别的项目 import 它?不希望,就放进 internal,让编译器替你把关。

3.3 /pkg:公开库代码的显式承诺

/pkg 的定位与 internal 恰好互补(见 pkg/README.md):

  • /pkg允许外部应用使用的库代码(如 /pkg/mypubliclib)。外部项目 import 这里时"期望它一直能工作",因此往里放任何东西之前都要三思;
  • internal 在强制私有性上更可靠(编译器执行),而 /pkg 的价值在于显式沟通:目录名本身就是"这些代码可以安全地被别人使用"的声明;
  • 另一个实际收益:当根目录里堆满大量非 Go 组件时,/pkg 把所有 Go 库代码集中到一处,方便运行各类 Go 工具;
  • 这个模式并非社区共识——README 坦承"它被广泛使用,但并非被普遍接受,Go 社区中有人不推荐它"。对很小的应用项目,多一层嵌套可能没有价值;当根目录开始变得杂乱(尤其是非 Go 组件很多时)再考虑引入;
  • 历史渊源:早期 Go 源码仓库自己就用 pkg 组织包,社区项目随后大量模仿了这一模式。

仓库模板中以 pkg/_your_public_lib_/ 占位,与 internal 形成"公开 vs 私有"的对照结构。

3.4 /vendor:依赖目录与模块代理

关于依赖管理,README.md 给出了明确的演进路线:

  • go mod vendor 命令会为项目生成 /vendor 目录;若 Go 版本低于 1.14,go build 时可能需显式加 -mod=vendor 标志;
  • 构建库(library)时不要提交应用依赖——这是明确写出的规则;
  • 自 Go 1.13 起,模块代理(默认使用官方 Go 模块代理服务器)已启用。如果代理满足你的需求与约束,可以完全不需要 vendor 目录

四、服务应用与 Web 应用目录

4.1 /api:接口契约的定义层

存放 OpenAPI/Swagger 规格文件、JSON Schema 文件、协议定义文件(见 api/README.md)。将接口契约文件独立成目录的好处是:契约与实现分离,代码生成、多语言客户端、API 网关配置等都以 /api 下的文件为单一事实来源。

4.2 /web:前端与模板资产

存放 Web 应用专属组件:静态 Web 资源、服务端模板、SPA(见 web/README.md)。仓库模板进一步给出了推荐的三段式子结构:

web/
├── app/        # Web 应用代码(如 SPA 构建产物/前端源码)
├── static/     # 静态资源(CSS、JS、图片等)
└── template/   # 服务端模板

这为"服务端渲染模板 + 静态资源 + 独立 SPA"三种常见形态各留了位置,可按需裁剪。

五、通用应用目录:配置、脚本与部署

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

放配置文件模板或默认配置;README 特别指出 confdconsul-template 的模板文件也应放在这里。仓库中 configs/README.md 与主文档一致。

5.2 /init:系统启动与进程管理

放系统 init 配置(systemd、upstart、sysv)与进程管理器/监管器配置(runit、supervisord)(见 init/README.md)。这类文件通常不随代码构建,但对部署交付不可或缺,集中管理可避免散落在仓库各处。

5.3 /scripts:把根 Makefile 变薄

/scripts 存放执行构建、安装、分析等各类操作的脚本(见 scripts/README.md)。README 给出了这一目录的核心价值主张:

这些脚本让根目录的 Makefile 保持小而简单。

仓库的 Makefile 本身就是这一理念的实证——整个文件只有一行注释:

# note: call scripts from /scripts

也就是说,所有实际逻辑都下沉到 /scripts 中的独立脚本,根 Makefile 只做薄薄一层转发。这种"Makefile 即目录"的做法让 CI 与本地开发都能直接调用 scripts/ 下的脚本,无需解析复杂 target 依赖。

5.4 /build:打包与 CI(文档定义目录)

/build 用于打包和持续集成,README 进一步细分为两个子目录:

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

需要说明的是:当前仓库的目录模板中并未创建 /build,它属于文档层面推荐的可选结构。

5.5 /deployments:编排与 IaC 配置

存放 IaaS、PaaS、系统级与容器编排的部署配置和模板,README 列举的典型工具包括 docker-compose、kubernetes/helm、terraform。deployments/README.md 中还额外列出了 mesos 与 bosh。README 同时提醒:部分仓库(尤其是以 kubernetes 部署的应用)把这个目录叫 /deploy,阅读他人项目时两个名字都需认识。

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

/test 用于存放额外的外部测试应用与测试数据,结构完全自由(见 test/README.md)。大项目建议设一个数据子目录。两个值得记住的 Go 工具行为:

  • /test/data/test/testdata 可以让 Go 忽略其中内容(testdata 是 Go 工具约定的忽略目录名);
  • ._ 开头的目录/文件同样会被 Go 忽略——这也是仓库模板用 cmd/_your_app_/ 这类下划线占位目录的原因。

六、其余支撑目录

目录 说明(源自 README.md
/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/cmd 的边界值得注意:cmd 是对外发布的可执行程序,而 tools 是服务于本项目开发/运维的辅助程序,且被允许依赖私有代码——这正是 internal 编译器约束下少数合法的内部消费场景。

七、反模式:为什么项目里不该有 /src

README.md 专设 "Directories You Shouldn't Have" 一节解释了这个常见误用:

  • Go 项目里出现 src 目录,通常是因为开发者从 Java 世界迁移过来习惯了该模式。结论很直接:不要让 Go 项目长得像 Java
  • 不要与 Go 工作区(workspace)里的 /src 混淆:$GOPATH(非 Windows 系统下默认 $HOME/go)指向工作区,工作区顶层包含 /pkg/bin/src 三个目录,项目本身是 src 下的子目录。若你的项目里再套一层 src,最终路径会变成 /some/path/to/workspace/src/your_project/src/your_code.go 这种双重嵌套;
  • 自 Go 1.11 起项目可以放在 GOPATH 之外,但这不意味着 src 目录模式变得合理。

判断规则:如果仓库顶层出现了 /src,几乎可以断定是误用。

八、落地方式:克隆模板,按需裁剪

这套布局的官方使用方式在 README 中一句话概括:

Clone the repository, keep what you need and delete everything else! Just because it's there doesn't mean you have to use it all.

即:克隆本仓库作为起点,保留自己需要的目录,删掉其余部分。"目录存在"不等于"必须使用"——pkginternal 甚至 vendor 都没有在每个项目中出现。结合 Makefile 的薄转发设计与 go.mod 的占位模块路径,一个可执行的落地流程是:

  1. 克隆仓库后修改 go.mod 中的模块路径(第一段需含点号以兼容旧版本 Go 构建);
  2. README.md 的目录表逐项裁剪:只做单一 CLI 工具的项目可只保留 cmd + internal;开源库则保留 pkgdocs
  3. 把构建逻辑写入 /scripts,让根 Makefile 保持一行注释加少量转发;
  4. 命名与风格上,README 建议从 gofmtstaticcheck 两个工具入手(前者负责格式,后者为 golint 弃用后推荐的受维护 linter)。

README 末尾还提示:一个更"有主张"的项目模板(带示例配置、脚本与代码)当时仍处于开发中,因此本布局的定位始终是"目录骨架"而非"代码骨架"。

九、小结:一张决策清单

README.md 的要点压缩成可复用的判断顺序:

  • 学 Go / PoC / 小项目 → 单文件 main.go + go.mod,跳过本文全部结构;
  • 代码不想被外部 import → 放 internal,编译器替你强制;
  • 代码明确作为公开 API 对外提供 → 放 pkg,并承担"外部依赖它一直可用"的承诺;
  • 多可执行程序 → 一个程序一个 cmd/<name> 子目录,main 保持极小;
  • 接口契约、前端资产、配置、init、部署、CI、脚本、文档、示例、工具各自独立成目录,根目录只留 go.modMakefileLICENSE.md 与少量顶层 README;
  • 顶层绝不出现代 src

这套布局的价值不在于它强制了什么,而在于它为 Go 社区提供了一套共同词汇:无论团队最终采用其中多少目录,cmd / internal / pkg 的语义在几乎所有 Go 项目中含义一致,这本身就是协作成本的大幅降低。

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