多个模块一起改时,go.mod 不一定要被污染
Go Modules 解决了单个项目依赖管理问题,但当你同时开发多个本地模块时,仍然会遇到麻烦。比如 user-api 依赖 company-kit,你现在要同时修改两边。过去常见做法是在 user-api/go.mod 里写:
replace example.com/company-kit => ../company-kit
这能工作,但如果忘记删掉并提交,其他同事可能因为没有同样目录而构建失败。Go 1.18 引入的 workspace,也就是 go work,就是为这种本地多模块联调准备的。
go work 不替代 go.mod。每个模块仍然有自己的 go.mod,工作区只是告诉 Go 工具:这几个本地模块现在放在一起开发,优先使用本地版本。
创建一个工作区
假设目录如下:
dev/
├── company-kit/
│ ├── go.mod
│ └── logx/logx.go
└── user-api/
├── go.mod
└── main.go
company-kit/go.mod:
module example.com/company-kit
user-api/go.mod:
module example.com/user-api
require example.com/company-kit v0.1.0
在 dev/ 下执行:
go work init ./company-kit ./user-api
生成 go.work:
go 1.18
use (
./company-kit
./user-api
)
现在你进入 user-api 运行:
go test ./...
go run .
Go 会使用工作区里的本地 company-kit,而不是下载 v0.1.0。
添加和移除模块
添加模块:
go work use ./another-service
编辑后可以查看:
cat go.work
如果不想再使用工作区,删除 go.work 或离开工作区目录即可。也可以通过环境变量临时关闭:
GOWORK=off go test ./...
这很适合检查模块在没有本地工作区时是否仍然能按 go.mod 构建。
go work 和 replace 的关系
replace 写在某个模块的 go.mod 里,影响这个模块的依赖解析。go.work 写在工作区根目录,影响你当前本地工作区里的命令。
如果只是你个人联调多个本地仓库,用 go work 更干净。如果项目标准就是 monorepo 多模块结构,团队也可以提交 go.work。如果是临时替换某个 fork,并且这个替换是项目构建必须的规则,replace 仍然合理。
不要把两者混得太复杂。依赖解析路径越绕,排查越难。团队最好有明确约定:哪些场景提交 go.work,哪些场景只本地使用。
常见排查命令
查看当前是否启用了工作区:
go env GOWORK
查看模块列表:
go list -m all
如果你改了本地模块却没生效,通常是因为当前终端不在 go.work 覆盖范围内,或者 go.work 没有 use 那个模块。
可以重新添加:
go work use ./company-kit
然后再运行测试。
提交前要回到模块视角
工作区很方便,但它也可能让你误以为项目已经能独立构建。比如你在工作区里修改了 company-kit,user-api 测试通过了;但如果 user-api/go.mod 仍然依赖旧版本,CI 不使用工作区时就可能失败。
提交前可以做一次普通模块模式检查:
cd user-api
GOWORK=off go test ./...
如果这一步失败,说明 user-api 离开本地工作区后无法独立构建。你需要发布依赖模块新版本,或者更新 go.mod 到正确版本。
这个习惯很重要。go work 是开发便利,不是发布协议。开发时用它提高效率,合并前仍然要确认每个模块自己的依赖关系是完整的。
编辑器里的工作区也要统一
多模块开发时,编辑器打开哪个目录也会影响体验。如果你只打开 user-api/,语言服务可能只看到这个模块;如果你打开 dev/,并且 dev/go.work 存在,编辑器通常能同时理解 company-kit 和 user-api。
当你发现跳转定义跳到了模块缓存,而不是本地 company-kit,可以检查两件事:编辑器工作目录是否包含 go.work,以及终端里的 go env GOWORK 是否指向预期文件。
这类问题不属于 Go 代码 bug,但会严重影响开发效率。多模块项目最好在 README 里写清楚推荐打开的根目录和初始化命令:
go work init ./company-kit ./user-api
go test ./...
让每个新人用同一套入口,能减少很多“我这里为什么跳转不对”的沟通成本。
这也是团队工程体验的一部分。
小结
go work 解决的是本地多个 Go 模块一起开发的问题。它让你不必为了联调修改每个模块的 go.mod,也减少了临时 replace ../path 被误提交的风险。
它不是依赖版本发布机制。真正合并和发布时,仍然要让被依赖模块发布版本,并让业务模块更新 go.mod。工作区是开发时的便利工具,理解这个边界,用起来就会很顺。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。