不该。过早拆分为多个go module会增加版本协同、replace hack、CI变慢等负担,而未解决核心耦合;应先通过职责分层(domain/application/infrastructure)、接口抽象、依赖注入和包级隔离实现逻辑解耦,再按需渐进升级。

Go单体项目该不该立刻拆成多个go module
不该。过早物理拆分为多个 go module 会引入版本发布协同、replace 临时 hack、CI 构建变慢、IDE 跳转卡顿等实际负担,而并未解决核心耦合问题。
真正要解耦的不是目录结构,而是包间依赖关系。先做到:domain 不 import infrastructure,application 不硬编码 MySQL 驱动,cmd/main.go 才是唯一能同时 import 上下层的地方。
- 用
go list -f '{{.Deps}}' ./... | grep infrastructure检查 domain 包是否意外依赖了数据库或 HTTP 库 - 所有跨层调用必须通过接口(如
UserRepository),接口定义放在调用方所在层(通常是application或domain) - 禁止在
internal/xxx下直接 importgithub.com/some-redis-driver—— 这类实现应只出现在infrastructure/redis
怎么设计可测试、可替换的接口层
接口不是为了“面向接口编程”而写,而是为了让某一层不感知技术细节。比如支付逻辑不该知道是调微信还是支付宝,订单创建不该知道用户数据存 MySQL 还是 TiDB。
关键点在于:接口定义位置、方法签名粒度、是否带 error、是否暴露底层类型。
立即学习“go语言免费学习笔记(深入)”;
- 接口定义放在调用方包里(例如
application中声明type PaymentService interface { Charge(...)),而不是在infrastructure里“先写实现再抽象” - 避免返回
*sql.Rows或redis.Cmd等具体类型;统一用 DTO 或 domain struct - 每个方法只做一件事,不要把
CreateAndNotify这种复合操作塞进一个接口方法 - 测试时用内存实现(
memPaymentService)或gomock生成桩,无需启动 Redis 或真实支付网关
依赖注入到底该在哪做
只在 cmd/main.go 或应用启动入口做。这里是你唯一可以同时看到所有层的地方,也是组装依赖的合法位置。
别在 application 包里 new 一个 mysql.UserRepo,也别用全局变量或 init 函数隐式注入。
- 构造函数注入最直观:
app.NewOrderService(repo userrepo.UserRepository, ps paymentsvc.PaymentService) - 如果参数多,用 Option 模式:
app.NewOrderService(app.WithUserRepo(...), app.WithPaymentSvc(...)) - 不用 DI 框架也能解耦;Wire 是可选增强,不是必需品;手写构造函数反而更易读、调试、单元测试
- 所有 service 初始化必须显式传参,禁止 “从 config 里读完就自动连上 DB” 这类隐藏行为
go-zero 单体服务里如何组织多模块逻辑
go-zero 的 goctl api 生成器默认把所有 logic 堆在 logic/ 下,但这不是终点。你得主动按业务域切分,而不是等框架帮你分。
比如文件服务里有 upload、download、scan、clean 四个功能,不要全塞进 filelogic,而应:
- 按功能建子包:
internal/upload、internal/download,各自含Logic、Repo、DTO - 共享类型放
internal/types,而非重复定义UploadReq在两个包里 - API 层路由仍走 go-zero 自动生成的 handler,但 handler 内部调用的是你分好的子包 Logic,不是裸写 SQL 或 HTTP 请求
-
go.mod保持单 module,但通过internal/目录天然隔离外部引用 —— 这就是 Go 的模块化原语
真正的解耦难点不在代码怎么写,而在于每次新增功能时,能否忍住不往老包里加新函数,坚持新建包、定义接口、注入实现。这个习惯比任何工具都重要。


















