go.mod 应放在每个微服务模块目录而非根目录,以实现服务自治;pkg/ 公共库须独立模块化、禁止业务模型;proto 应按限界上下文拆分并由服务显式依赖;需用 go list 和自定义 linter 检测循环依赖与非法 import。

go.mod 文件该不该放在根目录
根目录放 go.mod 不是必须,反而容易让整个项目变成“伪单体”。真正合理的做法是:每个微服务模块(如 auth、payment)各自拥有独立的 go.mod,module path 形如 github.com/org/project/auth。
常见错误是只在根目录建一个 go.mod,然后把所有服务代码塞进 service/ 子目录——这会导致:go build ./... 一键编译全部,无法按需构建单个服务;依赖版本被全局锁死,auth 升级 zap v1.25 可能被迫拉上 payment 一起升级;CI/CD 无法做服务粒度的依赖扫描和漏洞告警。
- 每个服务模块应能独立
go mod tidy、go test ./...、go build -o auth ./cmd/auth - 根目录可保留一个空的
go.mod(仅用于replace本地开发调试),但不提交到生产分支 - CI 流水线应基于
cmd/*/main.go路径触发构建,而非根目录
公共库(pkg)到底放哪、怎么引用
pkg/ 目录不是“所有服务都能随便 import”的共享仓库。放错位置会立刻引发循环依赖或隐式耦合——比如 pkg/auth 里不小心引入了 payment 的结构体,auth 服务就再也无法独立部署。
真正安全的公共库只做三件事:提供跨服务通用的工具函数(如 pkg/idgen)、定义通信契约(如 pkg/proto 下的 .proto 文件)、封装基础设施客户端(如 pkg/redis 封装 redis.Client 初始化逻辑)。
- 禁止在
pkg/里放业务模型(如User或Order),这些必须由调用方按需定义 DTO,或通过 proto 定义 schema - 所有
pkg/模块也必须带自己的go.mod,例如github.com/org/project/pkg/idgen - 服务模块通过
require github.com/org/project/pkg/idgen v0.1.0显式声明依赖,而不是import "pkg/idgen"这种相对路径写法
如何避免 proto 共享导致全链路强耦合
把所有 .proto 放进一个 api/ 仓库,再让各服务 go get 它,是典型的“单点故障式共享”。改一个字段,auth、payment、notification 全得同步发版。
正确做法是:按限界上下文拆分 proto 模块,每个服务只定义并维护自己暴露的接口。例如 auth/api/v1/auth.proto 和 payment/api/v1/payment.proto 分属不同 module,互不 import。
- 下游服务(如 order)需要调用 auth,就只
require github.com/org/project/auth/api v1.2.0,且只用其中LoginRequest和LoginResponse - proto 编译生成的 Go 代码必须放在对应服务的
internal/下(如auth/internal/api/v1),禁止放到pkg/供全局 import - 使用
buf lint+buf breaking强制检查向后兼容性,防止不经意破坏契约
依赖图可视化与循环依赖检测怎么做
当服务数超过 5 个,光靠人眼很难发现 auth → user → notification → auth 这类隐式循环。必须用工具抓取真实 import 关系。
别只跑 go mod graph——它只显示 module 依赖,不反映包级 import。真正有效的是 go list -f '{{.ImportPath}}: {{.Deps}}' ./... 配合脚本提取 import 边,再喂给 Graphviz。
- CI 中加入
go list -deps -f '{{if not .Standard}}{{.ImportPath}}{{end}}' ./... | grep -v '^$' | sort -u,提前拦截非法跨模块 import(如auth/internal/service直接 importpayment/internal/repository) - 用
golang.org/x/tools/go/analysis写自定义 linter,禁止在auth/目录下出现"payment/"字面量 import - 每天定时跑一次
go mod graph | grep -E "(auth|payment|notification)" > deps.txt,导入 Mermaid 渲染成文档,团队共看
go.mod,而在于每次新增一个 import 时,是否清楚它会让哪个服务失去自治性。模块边界一旦模糊,回滚、扩缩容、安全审计就全变成连带动作。

















