拆分内部模块并用语义化版本维护依赖边界
把一个目录拆成独立 Go module,真正获得的不是“目录更整齐”,而是独立的发布、版本与兼容承诺。只有被多个消费者复用、需要独立演进并且公开 API 已经相对稳定的能力,才值得跨出原 module;仍频繁联动的业务实现保留在同一 module 或 internal 包里更合适。
本文从零搭建一个小项目:money 提供金额能力,order 是消费它的服务。两个目录各有 go.mod,本地用 go.work 联调,正式依赖则使用 money/v1.0.0 这类子目录语义化标签。
Go Modules 官方参考:https://go.dev/ref/mod
- 一个 module 对应一组共同发布、共同版本化的包,不要按每个 package 机械拆 module。
- 同仓库子目录模块的标签必须带目录前缀,例如
money/v1.0.0。 go.work只负责本地多模块组合,生产依赖仍应写入消费者的go.mod。- 兼容新增升级 minor,兼容修复升级 patch,破坏性变更升级 major;从 v2 开始模块路径带
/v2。
项目目标:把稳定能力拆成独立发布单元
示例仓库只有两个顶层目录:money/ 是可发布模块,包含公开包和只允许模块内部使用的 internal/validate;order/ 是消费模块。仓库根目录的 go.work 仅用于本地开发。
这三个边界不要混淆:
- package:同一目录中一起编译的 Go 源文件。
- module:一起发布、版本化和分发的一组 package,由根目录的
go.mod标识。 - internal:编译器约束的导入范围;它可以存在于 module 内,但不等于独立 module。

拆分前先检查三个条件:能力是否被至少两个独立消费者需要;能否用少量公开类型和函数表达契约;是否愿意为已发布的 v1 API 承担兼容责任。只满足“文件很多”并不是拆 module 的理由。
环境准备:创建两个独立 module
以下命令使用示例模块路径 example.com/acme/money 和 example.com/acme/order。真实项目应替换为可访问的仓库路径。
# 创建同仓库中的两个模块目录。 mkdir -p money/internal/validate order/cmd/order # 每个模块单独初始化 go.mod,形成独立发布边界。 cd money && go mod init example.com/acme/money cd ../order && go mod init example.com/acme/order cd .. # 工作区只服务本地联调,不替代消费者 go.mod 中的正式版本。 go work init ./money ./order
完成后,money/go.mod 和 order/go.mod 各自声明模块路径;根目录 go.work 的 use 列表让 Go 命令在本地优先使用这两个工作区模块。不要为了联调把永久的本地绝对路径写进可发布配置。
核心代码:只暴露稳定的金额契约
money 模块公开 Amount 和构造函数,把货币代码校验藏在 internal/validate。这样消费者依赖的是业务契约,而不是校验实现。
package money
import (
"fmt"
"example.com/acme/money/internal/validate"
)
// Amount 以最小货币单位保存金额,避免浮点精度进入公开契约。
type Amount struct {
Cents int64
Currency string
}
// New 构造合法金额;校验细节保留在模块内部。
func New(cents int64, currency string) (Amount, error) {
if cents
internal/validate 可以被 money 模块内的包使用,但位于允许父目录之外的消费者无法导入它。这个限制能阻止 order 绕过公开 API 绑定内部细节。
package validate
import "fmt"
// Currency 只接受示例项目支持的货币代码。
func Currency(code string) error {
switch code {
case "CNY", "USD":
return nil
default:
return fmt.Errorf("unsupported currency: %s", code)
}
}
消费模块只导入 example.com/acme/money:
package main
import (
"fmt"
"log"
"example.com/acme/money"
)
func main() {
// 消费者只依赖公开构造函数,不接触 internal 校验包。
total, err := money.New(2599, "CNY")
if err != nil {
log.Fatal(err)
}
fmt.Printf("order total: %d %s\n", total.Cents, total.Currency)
}
本地运行:用 go.work 联调,不提交临时 replace
工作区启用后,可在仓库根目录同时测试两个模块。这里的目标不是证明发布版本可下载,而是快速验证当前工作树中的跨模块修改。
# 查看工作区中参与联调的模块路径。 go work edit -json # 对两个工作区模块执行测试,及时发现公开 API 改动造成的编译错误。 go test ./money/... ./order/... # 直接运行消费端,确认公开契约可以正常组合。 go run ./order/cmd/order
不要把 replace example.com/acme/money => ../money 当作正式依赖长期提交。replace 只在主模块中生效,发布 order 后不会替下游替你找到本地目录。团队可按仓库策略决定是否提交 go.work,但 CI 至少要额外在每个 module 目录独立执行测试,避免工作区意外掩盖缺失依赖。
部署与集成:用语义化标签替代本地路径
Go 模块版本以 v 开头,并遵循 major.minor.patch。由于 money 位于仓库子目录,版本标签需要加子目录前缀。第一次稳定发布可以这样准备:
# 先在 money 模块内整理依赖并运行测试。 cd money go mod tidy go test ./... cd .. # 子目录模块标签必须包含目录前缀,标签指向已提交的稳定快照。 git tag money/v1.0.0 git push origin money/v1.0.0
标签发布后,order 应记录正式版本依赖。私有仓库还需要在构建环境正确配置 GOPRIVATE 和凭据,但不要把令牌写进 go.mod、文章或脚本。
# 在消费模块中写入可复现的正式版本依赖。 cd order go get example.com/acme/money@v1.0.0 go mod tidy # 禁用工作区后再测试一次,确认发布依赖可以独立解析。 GOWORK=off go test ./...
最终 order/go.mod 应有明确的 require,而不是本地路径:
module example.com/acme/order go 1.25.0 // 正式构建依赖不可变的语义化版本,而不是开发机目录。 require example.com/acme/money v1.0.0

版本维护:让版本号直接表达依赖风险
| 改动 | 版本选择 | 模块路径 |
|---|---|---|
| 新增兼容函数,不影响旧调用 | v1.1.0 | example.com/acme/money |
| 修复实现错误,公开接口不变 | v1.1.1 | example.com/acme/money |
| 删除字段、改变参数或语义不兼容 | v2.0.0 | example.com/acme/money/v2 |
| 试验期且不承诺兼容 | v0.x.y | example.com/acme/money |
从 v2 开始,Go 要求主版本后缀进入模块路径。money/go.mod 需要声明 module example.com/acme/money/v2,消费者的 import 也改为对应路径。v1 与 v2 因路径不同,可以出现在同一个构建图中,这正是破坏性版本不会静默替换旧代码的关键。
不要用“内部模块”作为忽略语义化版本的理由。只要它被另一个 module 依赖,版本就是变更契约。私有仓库同样需要标签、变更说明和兼容策略;区别只是分发范围,不是依赖风险。
验收:同时验证工作区和发布边界
money与order各自拥有独立且可解析的go.mod。order只能导入money的公开包,无法导入其internal实现。- 工作区模式下,跨模块改动能够立即联调。
GOWORK=off时,消费者仍能依赖已发布标签完成测试。- 子目录标签使用
money/vX.Y.Z,版本号与公开 API 兼容级别一致。 - 破坏性版本同时修改 module path 和 import path,而不是只打一个 v2 标签。
常见问题
每个 package 都应该有自己的 go.mod 吗?
不应该。module 是发布和版本单元。一起发布、共享兼容周期的 package 放在同一 module 中,只有独立演进与复用压力明确时再拆。
有 go.work 后还需要 require 吗?
需要。go.work 组合本地模块,require 记录可复现的正式版本。禁用工作区的 CI 测试可以检查依赖声明是否完整。
子目录模块为什么不能直接打 v1.0.0?
同一仓库可能包含多个模块,标签必须用模块子目录作为前缀,Go 才能把版本映射到正确模块。本文示例应使用 money/v1.0.0。
私有模块需要语义化版本吗?
需要。语义化版本描述的是兼容性,不取决于仓库是否公开。私有模块还应配置 GOPRIVATE,避免把私有路径交给公共代理或校验服务。
这套结构的关键是把开发便利与发布契约分开:go.work 让同仓库修改保持顺畅,go.mod 和语义化标签让消费者获得稳定、可回滚、可审计的依赖边界。模块拆分因此不再只是目录重排,而成为明确的工程治理手段。
PHP Attribute 做路由元数据:读取、缓存与冲突处理
- 上一篇
- PHP Attribute 做路由元数据:读取、缓存与冲突处理
- 下一篇
- go mod tidy 为什么会加入看似未使用的模块
-
- Golang · Go教程 | 30分钟前 | Go教程 · 工程实践 · go.mod go.sum 间接依赖 go mod tidy Go Modules 依赖整理
- 整理 go.mod 间接依赖并解释 tidy 的增删结果
- 247浏览 收藏
-
- Golang · Go教程 | 45分钟前 |
- 通过 replace 临时联调本地依赖并在提交前移除替换
- 285浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- 把模糊测试发现的输入固化为长期回归用例
- 174浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- 用表驱动测试覆盖输入分区并生成清晰的子测试名称
- 302浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- 用 Cond 协调批量状态变化而不是循环轮询
- 204浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- go doc package@version 怎么查看指定依赖版本的 API
- 193浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Checker 如何在 Go 结构体上同时完成输入清洗和规则校验
- 299浏览 收藏
-
- Golang · Go教程 | 3小时前 | 标准库 · 配置管理 · 错误处理 · 并发编程 · Go教程 · Go 并发初始化 共享配置 sync.OnceValue sync.OnceValues
- 借助 OnceValue 延迟构造共享配置并传播初始化错误
- 482浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 363次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 419次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 433次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 385次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 210次使用
-
- Go error wrapping 实战:别让错误日志只剩一句 failed
- 2026-06-01 151浏览
-
- Go pprof 排查慢接口:别只会看火焰图,先把问题问对
- 2026-06-01 101浏览
-
- Go Flight Recorder 实战:线上偶发卡顿,别再只靠日志碰运气
- 2026-06-01 323浏览
-
- Go testing/synctest 实战:别再用 time.Sleep 赌并发测试会过
- 2026-06-01 428浏览
-
- Go slog 生产实践:日志别只会打印 error,要能帮你排障
- 2026-06-01 143浏览

