模块拆分应按业务边界而非技术分层,以“谁会变、为什么变、和谁一起变”为依据;各模块需接口隔离、路径唯一、replace配置正确、tidy需手动同步子模块、插拔靠接口+build tag、严禁循环依赖。

模块拆分必须对应明确的业务边界
模块不是按技术分层切的,而是按「谁会变、为什么变、和谁一起变」来划。比如支付模块里混进用户认证逻辑,后续改微信登录就得动支付代码——这就是边界模糊的典型症状。
真实项目中,每个 internal/xxx 目录应只暴露一个核心接口,例如 PaymentService,不导出结构体或中间类型。外部模块只依赖这个 interface,不 import 其内部实现包。
- 订单模块只处理状态机流转,不碰库存扣减逻辑
- 库存模块提供
Deduct(ctx, skuID, qty)方法,但不关心这笔扣减来自哪个订单系统 - 所有模块的 go.mod 中
module路径必须唯一,如example.com/order、example.com/inventory
本地开发阶段用 replace 指向子模块路径
多模块还没发版时,go get 会报错:找不到 example.com/inventory。这不是网络问题,是 Go 默认去代理或远程仓库找模块,而你本地只是文件夹。
必须在主模块(比如 cmd/app/go.mod)里显式写 replace:
replace example.com/inventory => ../inventory
注意三点:
-
replace的路径是相对当前go.mod文件的位置,不是项目根目录 - 被 replace 的模块自身也必须有
go.mod,且module名与 replace 左侧完全一致 - CI 构建前必须删掉
replace行,否则构建环境找不到本地路径
go mod tidy 不会自动同步 replace 模块的依赖
执行 go mod tidy 只整理当前模块的 require 列表,对 replace 指向的本地模块里的依赖变化完全无感。结果就是:子模块加了新 import,主模块编译失败,但 go mod tidy 一声不吭。
正确做法是进入每个子模块目录单独运行:
cd inventory && go mod tidycd order && go mod tidy- 再回到主模块目录跑一次
go mod tidy,它才会把子模块升级后的新依赖拉进来
漏掉这步,常见错误是 undefined: xxx.NewClient 或 cannot load xxx: cannot find module providing package。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
插拔能力靠接口抽象 + 构建时注入
可插拔不是靠 if-else 切换模块,而是让主程序只依赖接口,具体实现由构建时决定。比如日志模块:
定义统一接口:type Logger interface { Info(...interface{}) },放在 pkg/log 模块里;
不同实现分别在 internal/log/zap 和 internal/log/std,各自实现该接口;
主程序通过 build tag 控制编译哪一套:
go build -tags=zap -o app .
对应代码里写:
//go:build zap
package log
import "example.com/internal/log/zap"
func New() Logger { return zap.New() }
这种写法下,替换日志实现不需要改任何业务代码,也不需要重新设计 DI 容器。
真正容易被忽略的是:模块间不能有循环 import,哪怕只是间接依赖。一旦出现,go build 会直接报 import cycle,且错误位置往往藏在第三层依赖里,得用 go list -deps 一层层查。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










