登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

用 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.work 本地工作区与两个独立 Go 模块之间的静态边界图
图1:多模块工作区边界图。go.work 只组合本地源码,API 与服务仍由各自的 go.mod 定义发布身份。

联合开发时如何运行和测试

在工作区根目录,命令可以显式覆盖两个模块的包模式。不要直接假设根目录的 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 形成可获取的新版本,再让服务显式升级。

Go 工作区联合开发与 GOWORK off 独立发布验证矩阵
图2:独立发布验证矩阵。联合开发使用工作区,发布前则关闭工作区,分别核对模块依赖、测试与标签。

两个模块各自发布的稳妥顺序

当 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。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>