go mod replace 是临时重定向模块路径,仅在当前 module 生效,用于本地调试、修复未合入 bug 等场景,需确保路径、版本、module 名完全一致,且不替代 require。

为什么不能用 controllers/services/repositories 顶层目录?
这种结构会让所有业务的 handler、service、repo 全挤在一个目录里,改个用户登录逻辑得翻遍三个目录找关联文件,新成员三天内根本理不清调用链。Go 不是 Java,没义务模仿 Spring 的分层幻觉。
真正的问题在于:它模糊了业务边界。当你看到 services/ 下同时有 user.go 和 order.go,它们之间很容易悄悄耦合——比如 order.go 直接 new 一个 userRepo,而不是通过接口或 domain event。
- 每个业务功能(如 user、order、payment)必须自成一包,路径为
internal/user、internal/order - 包内再按职责拆文件:
user/service.go、user/repo.go、user/handler.go,但绝不跨包 import 实现细节 - 如果某个包 import 超过 3 个其他业务包,说明它已变成协调中心,该拆或重定义限界上下文
internal/ 和 pkg/ 的边界到底怎么划?
internal/ 是 Go 工具链强制的隔离机制:任何含 /internal/ 路径的包,外部 module 根本无法 import。这不是约定,是编译器级硬约束。
pkg/ 则是语义契约——它代表“我承诺稳定、无业务语义、可被其他项目直接依赖”。塞进 pkg/common 或 pkg/utils 这种名字,等于放弃契约;pkg/logger 或 pkg/httpx 才让人一眼知道用途和兼容性承诺。
立即学习“go语言免费学习笔记(深入)”;
-
internal/config、internal/middleware、internal/server可被多个cmd/复用,但绝不允许外部项目 import -
pkg/uuid、pkg/errors必须带完整单元测试和 godoc 注释,否则就别放pkg/ - 一旦发现
pkg/下某个包被 3 个以上业务模块强依赖,就要警惕它是否在演变成“上帝工具包”,考虑按场景进一步拆分(如pkg/db→pkg/db/gorm+pkg/db/pgx)
cmd/ 目录里 main.go 能写几行代码?
答案是:不超过 20 行。它只做四件事:解析 flag、加载 internal/config、构建 internal/server 实例、调用 .Run()。多一行,结构就开始失衡。
常见错误是把中间件注册、路由绑定、健康检查端点全堆在 main.go 里——这导致 cmd/api 和 cmd/worker 无法复用同一套启动逻辑,也破坏了构建时的可替换性。
-
cmd/api/main.go不 importinternal/user/handler,而是 importinternal/server,由后者注入 handler - 所有 HTTP 路由注册下沉到
internal/server,用 functional option 模式传入依赖(如server.WithHandler(user.NewHandler())) - 需要新增 CLI 工具?新建
cmd/cli/main.go,复用同一套internal/,只换入口函数和参数解析逻辑
go.mod 里的 replace 怎么用才不踩坑?
replace 不是临时补丁,而是模块化演进的正式阶段。它让单体项目能提前享受模块化开发体验:独立 go test、go vet、依赖隔离,而无需立刻拆 Git 仓库。
错误写法:replace github.com/xxx/project => ./internal/user —— 这会破坏路径一致性,且 go list -m all 无法识别模块归属。
- 在根
go.mod中声明伪模块路径:module example.com/project - 用 replace 映射子模块:
replace example.com/project/user => ./internal/domain/user - 其他模块 import 时必须用
example.com/project/user,而非相对路径;否则go mod tidy会报错或漏依赖 - 某天要拆出独立 repo 时,只需把
./internal/domain/user推到新仓库,打 v1.0.0 tag,删掉 replace,改用真实 module path 即可
最易被忽略的点:所有 replace 必须配合 go mod tidy 和实际构建验证。光写 replace 不跑 go build ./cmd/...,边界就守不住。


















