多模块本地联调不一定要改 go.mod
很多团队会把通用库和业务服务放在不同仓库或不同模块里。比如你正在开发 user-api,它依赖本地的 kit 模块。以前常见做法是在 user-api/go.mod 里写 replace:
replace example.com/kit => ../kit
这能工作,但如果你不小心把个人本地路径提交了,其他同事可能无法构建。go work 提供了另一种方式:在本地创建工作区,把多个模块临时组合起来开发,而不必修改每个模块的 go.mod。
这篇文章讲 go work 的入门用法和边界。它不是每个项目都必须用,但在多模块联调时非常舒服。
一个多模块目录
假设目录:
workspace/
├── kit/
│ ├── go.mod
│ └── logx/
│ └── logx.go
└── user-api/
├── go.mod
└── main.go
kit/go.mod:
module example.com/kit
user-api/go.mod:
module example.com/user-api
require example.com/kit v0.1.0
user-api/main.go 导入:
import "example.com/kit/logx"
如果没有工作区,Go 会按版本去找 example.com/kit v0.1.0。但你现在想使用本地 ../kit 的最新修改。
创建 go.work
在 workspace/ 目录:
go work init ./kit ./user-api
生成 go.work:
go 1.22
use (
./kit
./user-api
)
现在进入 user-api 运行:
go run .
Go 会优先使用工作区里的本地 kit 模块。你修改 kit/logx 后,user-api 能立刻使用新代码,不需要发布版本,也不需要改 user-api/go.mod。
添加模块:
go work use ./another-module
同步工作区:
go work sync
入门阶段最常用的就是 init 和 use。
go.work 要不要提交
这取决于团队约定。如果仓库本身就是一个 monorepo,包含多个模块,并且大家都应该用同一个工作区,提交 go.work 是合理的。
如果只是你个人本地同时开发两个独立仓库,go.work 更像本地开发辅助文件,不一定适合提交。否则别人拉到后,路径结构不一致就可能困惑。
一个简单判断:go.work 描述的是项目标准结构,还是你个人机器上的临时组合?前者可以提交,后者不要提交。
go work 和 replace 的区别
replace 写在模块自己的 go.mod 里,会影响所有使用这个模块构建的人,除非它没有被提交。go work 写在工作区文件里,更适合本地多模块联调。
replace 仍然有用途。比如你要临时替换某个依赖到 fork 版本,并且这个替换就是项目当前需要的构建规则,可以写进 go.mod。但如果只是本地开发两个模块,go work 通常更干净。
不要同时在多个地方写复杂替换规则。依赖解析越绕,排查越难。团队应该明确当前推荐方式。
一个真实联调流程
假设你发现 user-api 调用 kit/logx 时缺少一个字段。你可以先在 workspace 里创建工作区:
go work init ./kit ./user-api
然后修改 kit/logx:
package logx
func Fields(service string, requestID string) []interface{} {
return []interface{}{
"service", service,
"request_id", requestID,
}
}
在 user-api 里直接使用:
logger.Info("request finished", logx.Fields("user-api", requestID)...)
运行:
cd user-api
go test ./...
go run .
如果测试通过,你可以分别提交两个模块的改动。发布 kit 新版本后,再让 user-api 的 go.mod 升级到正式版本。这个流程比在 user-api/go.mod 里留下本地 replace 更干净。
需要注意的是,工作区只解决本地联调,不等于版本发布。CI 环境如果没有使用同一个 go.work,仍然会按 go.mod 里的版本解析依赖。因此合并前要确认依赖模块已经发布,或者 CI 也明确使用工作区结构。
常见问题:为什么我改了本地模块却没生效
遇到这种情况,先执行:
go env GOWORK
如果输出为空,说明当前命令没有处在工作区中。你可能不在 go.work 所在目录或其子目录下,也可能显式关闭了工作区。确认目录后再运行:
go list -m all
看依赖模块是否指向工作区版本。还要检查 go.work 里是否真的 use 了你修改的模块:
go work use ./kit
另一个常见原因是编辑器或终端打开了不同目录。Go 工具根据当前工作目录向上查找 go.work,目录不对时就会按普通模块模式工作。多模块项目里,保持终端、编辑器和测试命令的工作目录一致,会少很多困惑。
小结
go work 适合多个本地 Go 模块一起开发。它通过 go.work 文件声明工作区里的模块,让 Go 工具优先使用本地代码,减少临时 replace ../path 对 go.mod 的污染。
它不是每个项目的必需品。单模块项目不需要;个人临时工作区不一定提交;monorepo 多模块项目可以把它作为标准开发方式。理解它解决的问题,比机械使用命令更重要。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。