结论是不拆依赖树,先拆包结构;真要物理拆module,必须守住go指令版本、接口契约和replace使用边界。因为硬拆会导致编译失败、结构体重复定义、测试无法mock,根本原因是职责混杂与隐式依赖,物理拆module只是放大器而非解药。

直接说结论:不拆依赖树,先拆包结构;真要物理拆 module,必须守住 go 指令版本、接口契约和 replace 的使用边界。
为什么不能直接按业务名硬拆成多个 go module
多数人一上来就想把“用户”“订单”“支付”各自建 repo、各开一个 go.mod,结果很快遇到三类问题:
- 主项目编译失败,报错
cannot find module providing package xxx—— 因为跨 module 调用时,Go 不再自动推导本地路径,必须显式require且版本号对得上 - domain 层的
User结构体在 application 和 infrastructure 中被重复定义,或因字段变更导致两个 module 编译不一致 - 测试无法 mock,因为
infrastructure/mysql包里直接 new 了sql.DB,application 层又硬依赖它,接口没抽出来,DI 失效
根本原因不是代码多,而是职责混杂 + 依赖隐式穿透。物理拆 module 是放大器,不是解药。
先做包级隔离:从 main.go 开始砍依赖链
打开你当前的 cmd/main.go 或入口文件,检查 import 列表。如果看到类似这样的行:
import (
"database/sql"
_ "github.com/lib/pq"
"net/http"
"github.com/gorilla/mux"
"yourproject/internal/handler"
"yourproject/internal/repo"
"yourproject/internal/service"
)
说明基础设施细节(driver、router)和业务逻辑(handler、service)全挤在一个入口里。正确做法是:
- 把
handler、service、repo各自挪进语义明确的包,比如application、infrastructure/http、infrastructure/postgres - 确保
application只 importdomain和自己定义的接口(如repository.UserRepository),绝不 importsql或http - 用
go list -f '{{.Deps}}' ./application | grep infrastructure检查有没有反向依赖;有就删掉,补接口
这一步做完,你的依赖树会从“蜘蛛网”变成“单向流水线”:domain ← application ← infrastructure。
升级到多 module 的唯一合理时机与操作
只有当某个子系统满足以下全部条件时,才考虑独立为 module:
- 它被至少两个以上项目复用(不只是本项目内部调用)
- 它的 API 已稳定超过 3 个迭代周期,
go.mod中的go指令版本不再频繁变动(例如固定为go 1.21) - 团队已约定好 breaking change 的发布流程(如必须升 v2 路径、配套文档、兼容层)
操作上严格按顺序来:
- 在新目录下执行
go mod init github.com/yourorg/payment,立刻写好go 1.21 - 只导出 interface 和 DTO(如
type PayRequest struct{...}),不导出具体实现(postgres.PayRepo留在原项目) - 主项目用
replace github.com/yourorg/payment => ./payment本地调试,CI 中改用require github.com/yourorg/payment v0.3.1
注意:replace 只在本地生效,CI 和其他协作者必须走正式版本号,否则 go.sum 校验失败。
最容易被忽略的兼容性陷阱
很多人以为只要 go.mod 里写了 go 1.21 就万事大吉,其实还有三个隐形雷:
-
toolchain指令缺失:Go 1.21+ 强制要求该字段存在,否则 CI 构建可能用错工具链;补上toolchain go1.21更稳妥 - 第三方库悄悄用了 Go 1.22 新语法(如
~模式匹配),但你的go.mod还是go 1.21,本地能编,CI 报错 - 跨 module 接口实现没被编译器检查:A module 定义了
interface{ Save() error },B module 实现了它,但 B 没 import A —— 编译不报错,运行 panic
最后一句:90% 的 Go 单体项目,保持单 go.mod + 清晰包结构,比硬拆 module 更可持续。模块化不是目录数量,是依赖能不能被替换、被测试、被演进。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











