用 go.work 同时开发两个模块并保持各自发布独立
来源:17golang原创
时间:2026-10-07 13:01:22 201浏览 收藏
两个 Go 模块可以放在同一仓库或相邻目录中,用 go.work 做本地联合开发,同时继续各自发布。关键是把工作区看成本地源码选择层:它让服务模块在开发时直接读取 API 模块源码,但不改写任何模块路径,也不要求把本地 replace 写进 go.mod。发布前再用 GOWORK=off 分别测试,才能确认每个模块只依赖真实可发布的版本。
官方教程:https://go.dev/doc/tutorial/workspaces
- 两个目录分别保留自己的
go.mod、go.sum和版本标签。 - 仓库根目录运行
go work init ./api ./service,不要向模块文件加入本地 replace。 - 日常联调用工作区源码;发布前在每个模块内使用
GOWORK=off测试。 - API 先发布新版本,服务再关闭工作区升级 require,最后独立发布服务。
规模变大后,问题通常出在本地 replace
只有一个模块时,依赖边界很直观。拆出 API SDK 或公共库后,开发者往往在服务模块的 go.mod 中临时加入 replace corp.example/api => ../api。它确实能联调,却把个人目录结构写进了发布元数据:忘记删除会影响 CI,删除与恢复又会制造无意义差异,多人并行时还容易反复冲突。
go.work 把这类本地组合提升到模块之外。工作区的 use 指令声明哪些磁盘目录作为主模块参与当前命令;每个模块内部仍然保留自己的真实 require。这样,本地研发速度与发布契约不再由同一份文件承担。
工作区只负责本地组合,不合并模块身份
假设仓库中有 api 和 service 两个模块,服务线上依赖 API 的 v0.3.0,目录可以保持下面的结构:
# 两个模块各自保留发布文件,根目录只放工作区配置
repo/
├── go.work
├── api/
│ ├── go.mod
│ └── client/
└── service/
├── go.mod
└── cmd/server/
service/go.mod 应记录消费者真正能够获取的模块版本,而不是本地相对路径:
// 服务模块的发布契约:依赖一个已经存在的 API 版本
module corp.example/service
go 1.23.0
require corp.example/api v0.3.0
然后在仓库根目录创建工作区:
# 创建工作区,并一次纳入两个已有模块
go work init ./api ./service
# 确认当前目录实际使用的工作区文件
go env GOWORK
生成的 go.work 本质上只是本地组合清单:
// 工作区配置:只选择本地模块,不改变它们的发布身份
go 1.23.0
use (
./api
./service
)
此时服务导入 corp.example/api/client,Go 命令会优先使用工作区中的 ./api 源码。即使 API 的未发布改动尚未打标签,服务也可以立刻编译联调;而 service/go.mod 仍明确表示它对外发布时依赖 v0.3.0。

联合开发时如何运行和测试
在工作区根目录,命令可以显式覆盖两个模块的包模式。不要直接假设根目录的 go test ./... 会遍历所有模块,因为根目录本身通常不是一个模块。
# 在工作区上下文中联合测试两个模块
go test ./api/... ./service/...
# 新增第三个模块时,把它加入现有工作区
go work use ./worker
# 查看工作区里当前纳入的模块目录
go work edit -json
联合测试的价值是尽早发现跨模块接口变化。例如 API 修改了参数类型,服务会直接基于本地源码编译,不必先发布一个临时版本。但这只证明“两个当前目录一起工作”,还没有证明任何一个模块能单独发布。
用 GOWORK=off 验证真正的独立发布
go env GOWORK 会显示当前生效的工作区文件;设置 GOWORK=off 可以明确关闭工作区模式。发布门禁应该在每个模块目录分别关闭工作区,防止本地源码掩盖缺失版本或漏提交内容。
# 先验证 API 模块可以脱离工作区独立构建
cd api
GOWORK=off go mod tidy
GOWORK=off go test ./...
# 再验证服务只依赖 go.mod 中已发布的 API 版本
cd ../service
GOWORK=off go mod tidy
GOWORK=off go test ./...
如果工作区测试成功而服务的独立测试失败,通常说明服务使用了 API 的未发布接口。这个失败不是工作区的缺陷,恰恰是它揭示出的发布顺序问题:先让 API 形成可获取的新版本,再让服务显式升级。

两个模块各自发布的稳妥顺序
当 API 的本地改动已经被服务验证,可以先在 API 模块完成兼容性检查、测试和版本发布。假设新版本为 v0.4.0,服务随后关闭工作区升级真实依赖:
# 在服务模块中关闭工作区,并升级到刚发布的 API 版本
cd service
GOWORK=off go get corp.example/api@v0.4.0
# 整理服务模块自己的依赖账本并验证
GOWORK=off go mod tidy
GOWORK=off go test ./...
确认 service/go.mod 和 service/go.sum 的差异后,再给服务模块打自己的标签。API 和服务的版本号无需相同:API 可以发布 v0.4.0,服务仍按自己的语义版本发布。go.work 不参与消费者的模块下载,也不替两个模块决定标签。
为什么不建议日常直接运行 go work sync
go work sync 会根据工作区构建列表,把选中的依赖版本同步回各工作区模块的 go.mod。它适合团队明确要统一一批共享依赖版本的场景,但不是“让本地 API 生效”的必要步骤。仅仅进行本地跨模块开发时,use 已经足够。
如果无意中运行 sync,两个模块可能同时出现依赖版本变化,扩大审查范围,甚至让原本能够独立维护的模块产生不必要的版本耦合。需要使用时,应先确认目标版本,再逐个审查每份 go.mod 差异;不要把它当成 tidy 的工作区版本。
go.work 是否应该提交到仓库
Go 官方参考通常不建议提交 go.work:它可能覆盖开发者在父目录设置的工作区,也可能让 CI 测到与模块 go.mod 不同的依赖版本。对于发布边界清晰、可独立消费的模块,把 go.work 加入忽略规则往往更稳妥。
如果这是一个所有模块都强绑定、所有命令都以仓库根目录为入口的统一仓库,团队也可以约定提交工作区文件。无论选择哪种策略,发布 CI 都应增加 GOWORK=off 的逐模块测试;这比争论文件是否入库更能守住独立发布能力。工作区还可能生成 go.work.sum,它补充记录工作区所需但未被各模块 go.sum 集体覆盖的校验和,也不应被误认为某个模块的发布文件。
上线后的运行信号与后续改进
| 信号 | 说明 | 处理方式 |
|---|---|---|
| 工作区测试通过,GOWORK=off 失败 | 使用了尚未发布的跨模块改动 | 先发布被依赖模块,再升级依赖方 |
| 两个 go.mod 同时大量变化 | 可能误用了 go work sync | 确认是否真的要统一版本并审查差异 |
| CI 与本机选中版本不同 | 工作区或工具链环境不一致 | 记录 go env GOWORK,并在发布任务关闭工作区 |
| 服务 go.mod 出现本地 replace | 本地路径泄漏到发布契约 | 移除 replace,改由 go.work 的 use 管理 |
随着模块数量增加,可以把“工作区联合测试”和“关闭工作区的逐模块测试”拆成两个 CI 任务。前者保护跨模块集成,后者保护每个模块对外可消费。两类结果同时稳定,才说明本地开发效率和独立发布边界都没有被牺牲。
常见问题
go.work 会覆盖 service/go.mod 中的 require 吗?
不会改写文件,但在工作区模式下,本地 use 模块会作为主模块参与版本选择和包加载,所以命令使用本地源码。关闭工作区后,service/go.mod 中的正式版本重新成为独立构建依据。
可以在 go.work 中写 replace 吗?
可以,工作区级 replace 会覆盖工作区内模块的替换关系;当多个主模块存在冲突的 replace 时,也必须在 go.work 中解决。不过本例只需 use 两个本地模块,不需要额外 replace。
为什么不让两个模块共用一个 go.mod?
共用 go.mod 意味着它们成为同一个模块,通常共享模块版本边界。若 API 需要被其他项目独立引用,或者服务与 API 有不同发布节奏,保留两个 go.mod 更符合目标。
如何临时确认命令没有偷偷使用工作区?
先运行 go env GOWORK 查看路径,再在关键命令前显式加 GOWORK=off。不要只依赖当前目录位置推断工作区是否生效,因为 Go 会向父目录查找 go.work。
-
151 收藏
-
101 收藏
-
323 收藏
-
428 收藏
-
143 收藏
-
370 收藏
-
247 收藏
-
285 收藏
-
492 收藏
-
174 收藏
-
226 收藏
-
460 收藏
-
134 收藏
-
304 收藏
-
302 收藏
-
204 收藏
-
193 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习