cmd 目录应仅保留 main.go 用于启动,业务逻辑须移至 internal/ 领域包;internal 按领域聚类(如 internal/user/),pkg 仅放稳定对外契约代码,go.mod 的 replace 和 indirect 必须严格管控。

cmd 目录只放 main.go,别塞业务逻辑
很多人把路由注册、配置加载、中间件链全写进 cmd/main.go,结果一改就崩,测试难写,复用为零。正确做法是:它只负责解析命令行参数、加载基础配置、调用 internal/app.Run() 启动服务。哪怕单体项目,也建议抽一个 internal/app 包封装启动流程——这样未来拆成 cmd/api 和 cmd/worker 时,几乎不用动逻辑。
-
cmd下每个子目录名应与最终可执行文件名一致(如cmd/user-api→user-api) - 禁止在
cmd里 importinternal/handler或internal/service以外的深层包,否则会破坏分层边界 - 如果项目初期只有一个服务,
cmd/main.go可接受;但只要出现第二个入口(比如 CLI 工具),立刻建子目录,别拖
internal 是 Go 编译器强制的“私有墙”,不是心理安慰
把业务代码全丢进 internal 不等于工程化——关键是怎么组织。2026 年主流实践已从“按技术分层”转向“按领域聚类”。比如用户模块,所有相关代码(handler、service、repository、model)都放在 internal/user/ 下,而不是分散在 internal/handler/user.go + internal/service/user.go 里。
- Go 编译器会拒绝外部模块 import
github.com/your/project/internal/xxx,这是硬性保障,不是约定俗成 -
internal/pkg可用于项目内多服务共享的工具(如统一 logger 封装),但它仍不可被外部项目 import —— 这和pkg/有本质区别 - 常见错误:把 DTO(如
user.CreateRequest)放在internal/dto全局目录下,导致跨领域耦合;应随领域走,即internal/user/dto.go
pkg 目录不是“工具箱”,而是对外 API 的契约层
很多团队把所有能抽出来的函数都塞进 pkg/utils,结果半年后没人敢动这个包——因为谁也不知道哪个服务悄悄依赖了某个内部实现细节。2026 年推荐做法是:pkg 只放真正稳定、有明确语义、经过单元测试覆盖的公共能力,且每个子包必须有清晰的接口定义。
- 命名要具体:用
pkg/config、pkg/jwt、pkg/redisx,别用pkg/common或pkg/util—— 这类名字等于放弃维护责任 - 如果某个功能只被本项目两个服务用到,优先考虑放
internal/pkg/;只有确认会被其他项目(如公司内其他 Go 服务)直接 import 时,才升到pkg/ -
pkg下的代码必须避免 importinternal中的任何东西,否则就破坏了“可独立复用”的前提
go.mod 里 replace 和 indirect 项是工程稳定的命门
本地调试时用 replace github.com/xxx => ./internal/xxx 很方便,但上线前不清理,CI 构建就会拉错版本。更隐蔽的问题是 go.sum 里大量 // indirect 依赖没被显式 require,导致某天上游删掉一个旧 tag,整个构建失败。
- 所有
replace必须加注释说明用途(如// for local dev, remove before release),并纳入 PR 检查清单 - 定期运行
go mod tidy -v,关注输出中是否新增未声明的 indirect 依赖;如有,要么补require,要么确认是否真需要 - CI 流水线第一步应校验
go.mod是否干净:执行git status --porcelain go.mod go.sum,非空则失败 —— 防止开发者忘记提交依赖变更
internal 和 pkg 的职责边界是否被日常提交反复侵蚀,以及 go.mod 有没有人在意它到底锁住了什么。










