「svc 目录下有哪些文件、各自装什么内容、规范是啥?」—— 这篇把 internal/svc/ 这个「组合根」目录的职责、文件清单、每类文件的内容与写法一次讲清。
internal/svc/ 里最核心、goctl 必生成的是 servicecontext.go(装依赖的结构体 + 构造函数);随项目需要还会加 variables.go(类型安全的 ctx key 等常量)。它就是程序的「装配车间」,别的层只消费、不装配。
记得住的一句话:svc 目录只干一件事——把 config + 所有外部依赖(DB/Redis/RPC/HTTP/MQ)打包成一个 ServiceContext,通过构造函数一次性建好,再交给 handler / logic 用。文件少而精,没有 handler、没有 logic、没有路由。
它是「组合根」——整个程序依赖装配的唯一入口。
servicecontext.go 这一个文件就够了,不会散落十几个角落。ServiceContext,把 DB / RPC 字段换成 mock,logic 代码一行不用改。最小形态只有一个文件;随规模增长会自然长出 2~3 个。
| 文件 | 是否默认生成 | 装什么 | 谁会引用它 |
|---|---|---|---|
servicecontext.go | ✅ goctl 必生成 | ServiceContext 结构体 + NewServiceContext 构造函数 | main、handler、logic |
variables.go | ❌ 按需手建 | ctx key 类型与常量、共享枚举/常量 | middleware、logic |
group.go | ❌ 按需手建 | servicegroup 管理多 server 生命周期 | main |
逐文件给出内容与规范写法。
servicecontext.go —— svc 的心脏固定两块:结构体(挂所有依赖字段)和 构造函数(建一次、赋值)。字段命名见名知意,构造里只「建」不写业务。
package svc import ( "github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/zrpc" "github.com/zeromicro/go-zero/core/httpc" "your_project/user/internal/config" "your_project/user/rpc/user/user" ) // ① 结构体:把进程级依赖都挂上来 type ServiceContext struct { Config config.Config // 配置(只读) DB sqlx.SqlConn // 数据库 Redis *redis.Redis // 缓存 UserRpc user.User // 下游 RPC 客户端 PayHttp httpc.Service // 外部 HTTP 客户端 } // ② 构造函数:在这里把每个依赖建一次 func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, DB: sqlx.NewMysql(c.Mysql.DataSource), Redis: redis.New(c.CacheRedis), UserRpc: user.NewUser(zrpc.MustNewClient(c.UserRpc)), PayHttp: httpc.NewService(c.PayHttp), } }
svcCtx.Config.XXX;③ 构造里只建连接、不写业务逻辑;④ 依赖有先后时按字段顺序排,go-zero 不帮你排序。variables.go —— 类型安全的 ctx key 与常量把「放进/取出 ctx 用的 key」定义成私有类型常量,避免各文件重复定义、字符串 key 冲突。middleware 写入、logic 读取都引用这里。
package svc // 私有类型,杜绝与其他包 key 冲突 type contextKey string const ( // 中间件写入、logic 读取的请求级数据 key UserIdKey contextKey = "userId" TenantKey contextKey = "tenant" TraceKey contextKey = "traceId" ) // 也可以放服务级共享常量 / 枚举 const ( CacheExpire = 300 // 秒 OrderPrefix = "order:" )
variables.go 在 svc 包下,middleware 和 logic 都能 import(注意避免循环依赖——这里只放 key 和常量,不放任何依赖 svc 的东西)。实际上更常见是放 internal/middleware 或单独的 internal/ctxkeys 包;放 svc 是「约定俗成、就近」的选择,只要团队统一即可。group.go —— 多 server 生命周期(少见)当进程要同时跑 rest + zrpc 等多个服务、需要统一管理启停时才出现,用 service.ServiceGroup。
package svc import "github.com/zeromicro/go-zero/core/service" func Register(g *service.ServiceGroup, c config.Config) { // 把多个 server 加进同一个 group,统一 Start/Stop g.Add(rest.MustNewServer(c.RestConf)) g.Add(zrpc.MustNewServer(c.RpcConf, ...)) }
servicecontext.go 一个文件,别为了「看起来完整」硬加。svc 被「消费」,不反向依赖业务层。
箭头方向只有一个含义:config 供给 svc(svc 引用 config 类型),svc 供给 handler / logic(它们通过 svcCtx 字段用依赖)。svc 不 import handler / logic / types,所以 svc 永远是「底层、被依赖」,不会反向耦合业务逻辑。
团队统一遵守,避免 svc 变成大杂烩。
servicecontext.go 必备,variables.go / group.go 按需。不塞 handler、logic、路由、业务函数。svcCtx.DB 这样的访问,字段必须导出(首字母大写)。NewServiceContext 内只做「创建依赖 + 赋值」,不调用业务逻辑、不发请求、不处理错误(用 MustNewXxx 快速失败)。init(),否则顺序难以控制、测试难替换。「集中装配」是共识,但「专门为它建一个目录」是 go-zero 的强约束。
main() 里临时建,或塞进 engine / App 结构体。没有专门的 svc 目录,靠自觉,容易退化成全局变量。wire、container、infrastructure 这类「组装层」,理念同 svc,但不像 go-zero 用 goctl 强制生成固定文件名。@Configuration + @Bean 在类里声明装配,运行期 IoC 容器托管。没有目录级约定,靠注解。go-zero 是编译期、无反射、纯 struct。
· svc = 组合根,是程序依赖装配的唯一入口;
· 默认只有一个文件 servicecontext.go(结构体 + 构造函数);
· 按需加 variables.go(ctx key / 常量)、group.go(多 server 生命周期);
· 字段大写可导出、构造函数只建不写业务、不放 init();
· svc 被 config 供给、被 handler/logic 消费,绝不反向依赖业务层;
· 不放 handler / logic / 路由 / 请求级数据(请求级走 ctx)。