go.mod replace 是模块化拆解的必经隔离通道,需先用 replace 映射路径实现新旧代码并存,再通过 contract 接口、手动数据转换、独立测试等步骤完成安全拆包。

go.mod replace 是模块化拆解的第一步,不是可选配置
老旧项目直接改 import 路径会炸——编译报错、测试全挂、CI 失败。真正安全的起点是用 replace 在主 go.mod 里做路径映射,让新旧代码并存运行。这不是过渡技巧,而是必须走的隔离通道。
- 先在主
go.mod加一行:replace github.com/yourorg/project/user => ./internal/user,然后go mod tidy;此时所有原import "github.com/yourorg/project/user"仍能编译,但实际加载的是本地./internal/user - 别急着删老路径下的代码,先确保新包能独立
go test -v ./internal/user且通过 - 如果
go mod tidy提示"require internal/xxx: version is required",说明你漏了replace或路径拼写错误(比如少个/),不是版本问题
拆包时 import 路径和包名不一致,就会埋下循环依赖雷
Go 没有命名空间,import 路径就是模块标识。很多人把包建在 internal/user/service 下,却还在别的地方 import "github.com/yourorg/project/internal/user/service"——这等于把“内部实现”当公开接口暴露,后续一动就连锁崩。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 对外可引用的包,路径必须是稳定、语义化的,比如
github.com/yourorg/project/user,哪怕它物理位置在./internal/user -
internal/目录只用于存放真正不该被外部 import 的代码(如具体 DB 驱动、CLI 工具链),它的子目录不能出现在任何import语句中 - 检查循环依赖:运行
go list -f '{{.Deps}}' ./internal/order | grep user,如果同时看到user和order出现在彼此依赖列表里,说明接口没抽干净,得回退补定义 contract 接口
接口定义放错位置,拆包后类型不兼容是高频翻车点
把 type Order struct{} 留在老包里,新包里写 func Create(o *oldpkg.Order),看似能跑,但字段一改、tag 一加,所有调用方立刻报 cannot use o (type *oldpkg.Order) as type *newpkg.Order——这不是编译器刁难,是你没守住抽象边界。
- 每个新业务包根目录下建
contract.go,只放 interface 和 DTO:type OrderService interface { Create(input OrderInput) error }、type OrderInput struct { Name string `json:"name"` } - 老包实现该接口,新包只 import 新包自己的
contract.go,绝不 import 老包的 struct 定义 - 数据转换别靠反射或自动 mapping,初期手动赋值更可控:
newOrder := &newpkg.Order{ID: old.ID, Name: old.Name},一眼看出字段映射是否遗漏
测试没跟着拆,等于没拆
拆完包,go test ./... 还跑得通,但其实全是旧逻辑——因为测试文件还 import 着老路径,go test ./internal/user 实际执行的是 ./user_test.go 里对旧包的调用。
- 每个新包必须有同名
xxx_test.go,且package user(不是package main或package olduser) - 删掉旧包里所有测试文件,或重命名成
old_user_test.go并加// +build ignore注释,防止被误执行 - 新测试里禁止出现
sql.Open、http.ListenAndServe这类真实依赖,用 interface + mock,例如repo := &mockUserRepo{}实现UserRepository接口
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










