go项目中“模块”(go.mod)不等于“服务”,真正微服务拆分标志是各cmd/下独立main.go、专属db、不共享连接池与配置,能单独构建部署;逻辑分组(如internal/user)≠独立服务。

Go 项目里“模块”和“服务”不是一回事,go.mod 拆得再细,cmd/ 下只有一个 main.go,它就还是单体——哪怕你写了十个 internal/user、internal/order 包。
go.mod 多模块 ≠ 微服务拆分
很多团队在 go.mod 里按目录建了 github.com/org/project/user、github.com/org/project/order,以为这就是微服务。但只要所有模块被同一个 main.go 加载、共用一个 *sql.DB 实例、共享 config.yaml 里的数据库地址,那它们只是逻辑分组,不是独立服务。
- 验证方式:运行
go list -f '{{.Deps}}' ./cmd/main,如果输出里user和order出现在同一行依赖链中,且都引用了internal/db,说明没隔离 - 真正拆分的标志是:能单独
go build -o user-svc ./cmd/user,启动后只连自己的 DB,不读order表,也不依赖order/internal - 常见陷阱:把
shared/models提成独立 module,结果改个User.Email字段类型,所有服务全得重测上线——这比单体还脆
单体里用多 module 的合理场景
不是所有多 module 都错,有些恰恰是为了延缓拆分、降低耦合成本。关键看是否服务于“可独立部署”这个目标。
- 适合场景:
cmd/admin(后台管理 API)、cmd/api(用户端 HTTP)、cmd/cli(运维工具)共用一套业务逻辑(internal/service),但二进制分离——这样灰度发布时可只更新api,不影响后台 - 必须守住的线:各
cmd/xxx/main.go之间不能互相 import;internal/下的包不能暴露 DB 连接或 Redis 客户端,只暴露 interface,由各自 main 注入 - 危险信号:某个
cmd/xxx里调用了internal/order.NewService(db),而db是从全局变量或 config 里取的——这意味着它没法脱离其他模块独立启停
模块边界 vs 服务边界:DDD 限界上下文才是标尺
别拿目录名或 go.mod 名当服务名。“user” 模块可能包含登录、资料、权限三件事,但 AuthnService 和 UserProfileService 应该是两个服务——因为它们的数据变更频率、DB 类型、SLA 要求完全不同。
- 判断依据只有三个:
专属数据库(哪怕只是 schema 隔离)、独立 API 前缀(如/auth/v1vs/profile/v1)、上线不牵连(改密码策略不影响头像上传) - 典型错误:把 “用户中心” 当服务,结果
user.Service里既查users表,又发短信,又调支付回调——这不是服务,是上帝类 - 演进建议:先用
internal/auth和internal/profile划清接口(比如auth.Authenticator和profile.Reader),再通过wire分别注入到不同cmd/入口;等 DB 真的分库、团队分组后,再物理拆 repo
最常被忽略的是生命周期控制:一个服务能不能自己 sql.Open 又 db.Close,决定了它是不是真正的服务。共用连接池、共用配置文件、共用健康检查端点——这些细节比 go.mod 数量更能暴露真相。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











