首页
/ 深入解析 project-layout:标准 Go 项目目录结构的设计原则与实战参考

深入解析 project-layout:标准 Go 项目目录结构的设计原则与实战参考

2026-09-05 23:10:01作者:昌雅子Ethen

project-layout 仓库为 Go 应用项目定义了一套社区公认的目录布局规范,它并非 Go 核心团队制定的官方标准,而是对 Go 生态中历史沉淀与新兴目录模式的归纳总结。本文基于该仓库的完整布局文档,逐目录讲清每个目录的用途、适用时机与取舍边界,并结合仓库中实际存在的 go.modMakefileinternal/pkg/ 等骨架文件,给出可直接落地的目录组织方案。

一、定位与适用边界:先判断你是否需要这套布局

理解这套布局前,必须先明确它的三条定位声明:

  1. 它不是官方标准。这是 Go 生态中"常见历史模式 + 新兴模式"的集合,不同模式的流行程度并不相同;其中还包含若干小改进,以及一些足够大的真实世界应用都会用到的支撑目录。
  2. 刻意保持通用。它不试图强制某种特定的 Go 包结构(例如不会规定 Clean Architecture 的内部划分),而是提供目录级别的组织约定。
  3. 它是社区作品。发现新模式或认为某个现有模式需要更新时,应以 issue 形式提交反馈。

原文档特别强调了一个关键的反直觉建议——学习 Go、做概念验证(PoC)或个人练习项目时,这套布局是"过度设计"。正确的起点是极简的:单个 main.go 文件加一个 go.mod 已经足够。只有当项目成长后,才需要逐步引入结构化,否则你会陷入"大量隐式依赖 + 到处可访问的全局变量"的泥潭。

按项目阶段给出的演进建议:

  • 项目开始变大:确保代码良好组织,避免隐式依赖与全局状态;
  • 多人协作:需要更严格的结构,引入统一的包/库管理方式;
  • 开源项目、或有其他项目 import 你的代码:此时必须使用 internal 目录明确划定私有代码边界。

操作方式很直接:克隆仓库,保留你需要的目录,删掉其余一切。目录存在不代表你必须全部使用——连 vendor 模式都并非通用

二、Go Modules 与仓库中的 go.mod

文档指出,Go 1.14 起 Go Modules 正式达到生产可用级别:除非有明确理由不用,否则应使用 Go Modules,之后你不必再操心 $GOPATH 与项目放置位置。

对照本仓库实际的 go.mod 文件,可以看到它只有两行:

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

go 1.19

由此可以印证文档中的几个要点:

  • 仓库内的 go.mod 只是占位模板。模块路径写成了 YOUR-USER-OR-ORG-NAME/YOUR-REPO-NAME 的示意值,意味着它默认假设项目托管在 GitHub,但这不是硬性要求;
  • 模块路径的第一个组件应包含一个点(例如 github.com/...)。当前 Go 版本已不再强制此规则,但使用稍旧版本时,缺少点号可能导致构建失败。文档同时引用了 Go 上游 issue 37554 与 32819 作为背景依据(可查阅上游 Go 仓库对应 issue 了解细节);
  • 从源码结构看,本仓库声明 go 1.19,即该布局模板面向的是较新的 Go 工具链,读者复制此骨架时按自身项目实际 Go 版本调整即可。

三、命名、格式与风格工具链

在动手建目录之前,文档建议先解决命名与格式问题:

  • 工具:运行 gofmtgolint 处理命名、格式与风格(英文版 README 已补充说明 golint 已停止维护,推荐改用受维护的 staticcheck 等 linter);
  • 风格指南:阅读 Go 官方的命名规范(effective go 的 names 章节)、包命名博客文章、Code Review Comments 维基,以及 rakyll 总结的《Style guideline for Go packages》;
  • 延伸阅读(文档列出的 GopherCon 系列演讲,涵盖工业级编程最佳实践、Go 反模式、如何组织 Go 应用):Peter Bourgon 的《Best Practices for Industrial Programming》(GopherCon EU 2018)、Ashley McNamara 与 Brian Ketelsen 的《Go best practices》(GopherCon Russia 2018)、Edward Muller 的《Go Anti-Patterns》(GopherCon 2017)、Kat Zien 的《How Do You Structure Your Go Apps》(GopherCon 2018);
  • 中文补充:文档特别收录了一篇关于"面向包的设计和架构分层"的中文文章,以及介绍 Go Project Layout 背景知识的长文。

这些资源与目录布局共同构成完整的工程规范:目录解决"代码放在哪",风格工具解决"代码写得多规范"。

四、Go 核心目录

4.1 /cmd:主应用入口

/cmd 存放项目的主应用程序,规则有三条:

  1. 目录名与可执行文件名一致。每个应用的目录名应等于你期望的可执行文件名,例如 /cmd/myapp
  2. 不要把大量代码塞进应用目录。代码可被其他项目复用时放入 /pkg;不可复用或不想让别人复用时放入 /internal——文档提醒"别人会怎么用你的代码,会让你大吃一惊",因此意图必须显式表达;
  3. 典型的 main 函数很小,只做一件事:import 并调用 /internal/pkg 中的代码。

本仓库的 cmd/README.md 给出了该模式的真实参照项目,其 /cmd 目录下只有一个占位目录 cmd/your_app,即"你的应用名"模板位。参照项目包括 velero(极小的 main 函数 + 全部逻辑在包中)、moby、prometheus、influxdb、kubernetes、dapr、go-ethereum 等——可以看到 cmd 在大型项目中确实只承担入口职责。

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

/internal 存放私有应用代码与私有库代码,即你不希望其他项目 import 的代码。两个关键机制:

  • 由 Go 编译器本身强制执行(该约束自 Go 1.4 起引入,见 Go 1.4 release notes 的 internal packages 章节)。把包放入 internal 目录后,只有共享共同祖先的包才能 import 它;internal 也是 Go 官方文档中唯一指名给予特殊编译器待遇的目录;
  • 不限于顶层。项目树的任何层级都可以存在 internal 目录,可以有多个。

原文档给出了一种可选的二级结构(小项目不必引入,但它提供了"包用途"的视觉线索):

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

对照本仓库的实际骨架,internal/ 目录正好按这个二级结构组织,包含两个占位子目录:

internal/
├── app/
│   └── _your_app_/          # 实际应用代码位
└── pkg/
    └── _your_private_lib_/  # 应用间共享的私有库位

这正是文档中"可选额外结构"的可运行示例。internal/README.md 还列出了使用该模式的大型项目(terraform、influxdb、perkeep、jaeger、moby、minio 等),并单独为 /internal/pkg 补充了示例(如 hashicorp/waypoint 的 internal/pkg)。

4.3 /pkg:对外公开的库代码

/pkg 存放允许被外部应用使用的库代码(例如 /pkg/mypubliclib)。要点:

  • 其他项目 import 这里的库时默认它会一直可用,所以放入前要三思;
  • 确保"私有"的最可靠手段仍是 internal(编译器强制);/pkg 的价值在于**显式传达"此目录代码可安全对外使用"**的意图;
  • 当根目录充斥大量非 Go 组件与文件时,/pkg 还能把所有 Go 代码聚拢到一处,方便运行各类 Go 工具(GopherCon EU 2018 工业级编程最佳实践、Kat Zien 的演讲、GoLab 2018 的 Project layout patterns in Go 均提到此点);
  • 该模式并非社区共识。每个使用它的人气仓库,都能找到十个不用的;但"使用它的人知道它是什么意思"这一点本身降低了沟通成本。小项目或额外嵌套层级没有价值的场景,可以不用;当根目录变得拥挤(尤其是非 Go 组件多)时再考虑引入;
  • 来源考据pkg 目录的源头是早期 Go 源码自身用 pkg 组织包,社区项目随后纷纷效仿(文档引用了 Brad Fitzpatrick 的推文作为背景)。

本仓库骨架中 pkg/ 下只有一个占位目录 pkg/_your_public_lib_/,对应"你的公开库名"。pkg/README.md 附了一张长列表,展示了采用该模式的大众项目:containerd、moby、prometheus(见 cmd 列表)、kubernetes、helm、etcd、k3s、jaeger、istio、grafana、influxdb、cockroach、delve、argo-workflows/argo-cd、kubevela、kyverno、thanos、cri-o、linkerd2、cilium、kuma 等数十个仓库,可作为"哪些规模与领域的项目选择 pkg 模式"的实证参考。

4.4 /vendor:依赖目录的取舍

/vendor 存放应用依赖,可手工管理,也可用 Go Modules 这类内置依赖管理特性管理:

  • go mod vendor 命令会为你生成 /vendor 目录;
  • 若使用的 Go 版本早于 1.14,go build 可能需要显式加 -mod=vendor 参数(Go 1.14 起默认启用);
  • 写库(library)时不要 commit 依赖
  • 自 Go 1.13 起模块代理(module proxy)特性默认启用(默认代理为官方 proxy.golang.org)。如果你的网络与合规环境满足该代理的约束,则可以完全不需要 vendor 目录

这也解释了为何 vendor 不在本仓库的默认目录骨架里——文档原文即声明"即使 vendor 也不是人人都在用的模式"。

五、服务应用目录:/api

/api 存放 OpenAPI/Swagger 规范文件、JSON Schema 文件、协议定义文件。

api/README.md 给出的参照项目是 kubernetes 与 moby 的 /api 目录——两者都是"API 定义与实现分离、以定义文件为契约源头"的典型。

六、Web 应用目录:/web

/web 存放 Web 应用专属组件:静态 Web 资源、服务端模板与 SPA。

本仓库骨架中 web/ 目录直接给出了三分法结构:

web/
├── app/       # SPA 前端代码
├── static/    # 静态资源
└── template/  # 服务端模板

这比文档正文的三词描述更具体,可直接作为 Web 型 Go 项目的起步结构。

七、通用应用目录

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

存放配置文件模板或默认配置,confdconsul-template 的模板文件也应放在这里。对应仓库中的 configs/ 目录。

7.2 /init:系统与进程管理器配置

存放系统 init(systemd、upstart、sysv)与进程管理器(runit、supervisord)的配置。对应仓库中的 init/ 目录。

7.3 /scripts:构建与运维脚本

存放执行构建、安装、分析等各类操作的脚本。其设计意图是让根目录 Makefile 保持小而简单(文档以 hashicorp/terraform 的 Makefile 为例)。

这一设计意图在本仓库中有直接证据——根目录的 Makefile 只有一行注释:

# note: call scripts from /scripts

即根 Makefile 被刻意压到最小,所有实际操作下沉到 scripts/ 中的脚本。scripts/README.md 列出的参照项目(helm、cockroach、terraform)也全部遵循同一策略。

7.4 /build:打包与持续集成

/build 覆盖打包(Packaging)与持续集成(CI)两类配置:

  • /build/package:云镜像(AMI)、容器(Docker)、操作系统包(deb、rpm、pkg)的打包配置与脚本;
  • /build/ci:CI 工具(Travis、Circle、Drone)的配置与脚本。注意部分 CI 工具(如 Travis CI)对配置文件位置非常敏感——建议将配置统一放在 /build/ci,再在可行时软链到 CI 工具期望的位置。

7.5 /deployments:部署编排配置

存放 IaaS、PaaS、系统与容器编排的部署配置和模板:docker-compose、kubernetes/helm、mesos、terraform、bosh 等。注意:部分仓库(尤其是用 kubernetes 部署的应用)会把该目录命名为 /deploy。本仓库对应 deployments/ 目录。

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

/test 存放额外的外部测试应用与测试数据,内部结构可自由组织。两个实用规则:

  1. 大项目建议为测试数据设子目录;若需要 Go 忽略该目录内容,命名为 /test/data/test/testdata 即可(testdata 是 Go 工具链的约定忽略目录);
  2. Go 同样会忽略以 ._ 开头的目录与文件,因此测试数据目录的命名比 data/testdata 有更大的灵活度。

test/README.md 的参照示例(openshift/origin)将测试数据放在 /testdata 子目录,与上述规则一致。

八、其他目录

目录 用途 补充说明
/docs 设计与用户文档(godoc 生成文档之外) 参照 hugo、openshift/origin、dapr 的 docs/
/tools 项目辅助工具 这些工具可以 import /pkg/internal 中的代码;参照 istio、openshift/origin、dapr 的 tools/
/examples 应用与公开库的使用示例 参照 nats.go、docker-slim、packer 的 examples/
/third_party 外部辅助工具、fork 代码与其他第三方组件(如 Swagger UI) third_party/
/githooks Git hooks githooks/
/assets 图片、logo 等仓库级资源 assets/
/website 非 GitHub Pages 场景下,项目官网文件的存放地 website/

九、不应存在的目录:/src

文档专门辟出"你不应该拥有的目录"一节,结论是避免项目级 /src

  • 一些 Go 项目确实有 src 目录,但这通常发生在开发者来自 Java 世界的场景——Java 生态的 src 惯用法在 Go 中没有对应意义,"你真的不希望自己的 Go 项目看起来像 Java 项目";
  • 不要与 Go 工作区的 /src 混淆$GOPATH 指向你的工作区(非 Windows 系统上默认为 $HOME/go),其顶层包含 /pkg/bin/src 三个目录,真实项目是 $GOPATH/src 下的子目录。如果项目自身又有一个 /src,最终路径会变成 工作区/src/你的项目/src/你的代码.go 这种双层嵌套;
  • Go 1.11 之后项目可以放在 $GOPATH 之外(module 模式),但这不意味着项目级 src 是好主意。

十、徽章与生态工具

文档末尾列出可直接用于 README 的四类徽章及配置方法(替换为自己的项目标识即可):

  • Go Report Card:用 gofmtgo vetgocyclogolintineffassignlicensemisspell 扫描代码并出具质量报告卡;
  • GoDoc:提供 GoDoc 生成的在线文档(已标注为弃用,原文以删除线标示);
  • Pkg.go.dev:Go 包发现与文档的新入口,提供徽章生成工具;
  • Release:显示项目最新版本号。

其中 Go Report Card 与 Pkg.go.dev 对本模板仓库自身同样生效(仓库 README 中即挂载了对应徽章),可作为"布局模板本身也接受同等级质量扫描"的旁证。

十一、备注:更"有主见"的模板在路上

文档最后说明:一个包含示例/可复用配置、脚本与代码、更有主见(more opinionated)的项目模板仍在开发中(WIP)。当前版本刻意保持通用与低约束,后续若有更强的模板发布,可对照本文的目录清单做增量迁移。

十二、完整目录速查与译文

把全部目录按文档分类汇总如下,可直接作为新项目脚手架的核对清单:

project/
├── api/            # OpenAPI/Swagger、JSON Schema、协议定义
├── assets/         # 图片、logo 等资源
├── cmd/<app>/      # 主应用入口(目录名=可执行文件名,main 保持极小)
├── configs/        # 配置模板与默认配置
├── deployments/    # 部署编排配置(docker-compose、k8s/helm、terraform 等)
├── docs/           # 设计与用户文档
├── examples/       # 应用/公开库示例
├── githooks/       # Git hooks
├── init/           # systemd / supervisord 等 init 配置
├── internal/       # 私有代码(编译器强制隔离)
│   ├── app/        #   实际应用代码
│   └── pkg/        #   应用间共享的私有库
├── pkg/            # 对外公开库代码(可选,按需启用)
├── scripts/        # 构建/安装/分析脚本(保持根 Makefile 精简)
├── test/           # 外部测试应用与测试数据
├── third_party/    # 外部工具与 fork 代码
├── tools/          # 项目辅助工具(可引用 /pkg 与 /internal)
├── web/            # app / static / template
└── website/        # 项目官网文件(非 GitHub Pages 时)

根级文件:go.mod(模块声明模板)、Makefile(指向 scripts 的最小入口)、LICENSE.md

该仓库提供了完整的多语言文档,本文依据的土耳其语文档为 README_tr.md,其余译文包括 README.md(英文原版)、README_zh.mdREADME_zh-CN.mdREADME_zh-TW.mdREADME_ja.mdREADME_ko.mdREADME_ru.mdREADME_es.mdREADME_fr.mdREADME_it.mdREADME_ptBR.mdREADME_ro.mdREADME_vi.mdREADME_ua.mdREADME_id.mdREADME_hi.mdREADME_be.mdREADME_fa.md。各版本内容同源,可按阅读习惯选择;英文版 README.md 包含若干土耳其语文档未覆盖的补充(如 golint 弃用与 staticcheck 替换说明、internal 目录机制的原文引用),建议交叉阅读。

落地建议:按"从最小开始、按阶段加目录"的原则使用本布局——起步只有 cmd/<app>go.mod;出现跨项目复用的诉求时引入 internalpkg;进入团队协作与发布阶段后,再按部署形态补齐 configsinitdeploymentsscriptstest 等支撑目录。

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