cmd目录必须存在且只放main.go,因其是微服务唯一启动入口;handler/service等业务代码塞入main.go会导致测试难隔离、子服务无法按需启动、构建体积膨胀。

cmd 目录必须存在,且只放 main.go —— 这是 Go 微服务启动的唯一入口,其他任何业务代码都不能塞进去。
为什么不能把 handler 或 service 直接写进 main.go
一开始图省事把路由、数据库初始化、HTTP 启动全堆在 main.go 里,短期能跑,但很快会遇到三类问题:
• 单元测试无法隔离:没法 mock 数据库或中间件,go test 只能跑集成测试
• 无法按需启动子服务:比如只想启动 auth 服务做调试,却被迫加载整个用户模块
• go build -o 输出二进制时无法控制依赖范围,导致体积膨胀、启动变慢
Go 的包机制天然支持“编译时裁剪”,前提是代码分层清晰、依赖显式声明。把逻辑揉进 main.go 就等于放弃这个优势。
internal/ 下该拆哪几层?不是 MVC
Go 不需要也不适合照搬 Rails 风格的 controllers + models + views。推荐的最小可行分层是:
• internal/handler/:只做三件事——解析请求参数(c.ShouldBindJSON)、调用 service、构造响应(c.JSON)。不处理业务规则,不碰数据库
• internal/service/:真正写业务逻辑的地方。比如 “创建订单” 要校验库存、扣减余额、发消息,这些都在这里组合完成
• internal/repository/:只封装数据访问,比如 userRepo.FindByID 或 orderRepo.Create。它不知道 HTTP、不关心 JSON 格式,只和 gorm.DB 或 sqlx.DB 打交道
• internal/model/:纯结构体定义,带 GORM tag(如 gorm:"primaryKey"),不嵌入方法、不依赖框架
pkg/ 和 internal/ 的边界在哪?
简单判断标准:
• 如果某个工具函数(比如通用 JSON 解析器、重试策略、JWT 解码器)会被其他项目复用,就放 pkg/
• 如果只服务于当前微服务(比如 “用户密码加密逻辑”、“订单状态机转换规则”),哪怕看起来很通用,也放 internal/
• pkg/response 是常见例外:统一响应格式(Success(data) / Error(code, msg))通常放在 pkg/,因为前端 SDK 或其他微服务可能直接引用它
混淆点在于:有人把 pkg/logger 放错位置。Zap 实例本身应由 cmd/main.go 初始化并注入到 handler,而不是让 pkg/logger 自己全局 init —— 否则测试时无法替换日志输出目标。
Gin 路由注册必须用 Group 吗?
不是必须,但不用 r.Group("/api/v1") 会导致两个实际问题:
• 版本升级困难:v2 接口上线后,旧 v1 接口要继续维护,硬编码路径(r.GET("/users", ...))会让 v1/v2 混杂,路由表难以维护
• 中间件作用域失控:鉴权中间件如果挂到根路由 r.Use(authMw),就会拦截健康检查(/health)或指标接口(/metrics),而 apiGroup.Use(authMw) 可精准控制范围
更关键的是,Gin 的 Group 返回新路由引擎实例,其 Use 和 GET/POST 调用不会污染主引擎 —— 这是实现模块化路由的基础机制,不是语法糖。
立即学习“go语言免费学习笔记(深入)”;
真正的难点不在目录名怎么起,而在每个包的import 语句是否干净:如果 internal/handler 导入了 gorm 或 redis,说明职责已经泄漏;如果 internal/service 导入了 gin.Context,说明它被 HTTP 绑定了。这种耦合不会报错,但会在三个月后让你改一个字段就得测完整条链路。


















