Kratos 项目目录结构靠分层边界强制约束依赖流向:internal/service 和 internal/data 必须物理隔离,因 Wire 按包扫描且禁跨包循环引用;混写会导致 Wire 初始化失败、单元测试无法 mock、违反依赖倒置。

Kratos 项目目录结构不是靠“约定俗成”撑起来的,而是靠分层边界强制约束依赖流向——一旦越界,go build 会直接报错,Wire 生成器也会拒绝组装。
为什么不能把 service 和 data 放进同一个包?
Kratos 的 internal/service 和 internal/data 必须物理隔离,因为 Wire 的 Injectors 是按包扫描的,且默认禁止跨包循环引用。如果把 DAO 实现和业务逻辑混写在 service 包里,会导致:
-
Wire在生成App初始化代码时失败,报错类似cycle detected: service → data → service - 单元测试无法只 mock 数据层,因为 service 包直接 import 了具体数据库驱动(如
github.com/go-sql-driver/mysql) - 更换存储(比如从 MySQL 切到 TiDB)时,必须改 service 层代码,违反“依赖倒置”原则
api 目录里 proto 文件怎么组织才不踩坑?
api/ 下不能平铺所有 .proto,否则 kratos proto client 生成的 Go 代码会全塞进一个包,造成命名冲突和版本管理困难。正确做法是:
- 按服务域划分子目录:
api/user/v1/、api/order/v1/,每个子目录放一组语义完整的 proto - 每个
v1/目录下只保留一个xxx.proto(如user.proto),避免多个 proto 间import形成隐式依赖环 -
option go_package = "xxx/api/user/v1";中的路径必须与文件系统路径严格一致,否则protoc生成的RegisterXXXHandler函数注册不到 HTTP 路由
cmd/main.go 里哪些初始化顺序绝对不能颠倒?
Kratos 的 cmd/xxx/main.go 看似简单,但初始化顺序错一位,就可能让服务启动后 panic 或日志不输出。关键顺序是:
- 先调用
log.NewLogger(),再读配置(conf.Load())——否则配置加载失败时连错误日志都打不出来 - 数据层(
data.NewData())必须在 service 层(service.NewService())之前初始化,因为 service 构造函数依赖 data 接口实现 -
app.Run()必须放在最后,且不能包裹在defer里——否则信号捕获失效,Ctrl+C无法优雅退出
internal/ 下哪些目录能被 test 包直接 import?
只有 internal/service 和 internal/api 可以被 test 包直接 import;internal/data 和 internal/conf 必须通过接口或 Wire 注入器间接使用。这是为了保证测试可隔离:
- 测 service 时,用
gomonkey或接口 mock 替换 data 实现,不碰真实数据库 - 测 api 层时,只起 HTTP server,不初始化 data,避免测试环境连错 DB
- 如果 test 包直接 import
internal/data,就会强制拉入 MySQL 驱动,导致 CI 环境因缺少 DB 连接而失败
最常被忽略的是 internal/pkg 这类工具包的可见性——它不能依赖任何业务层(如 service 或 data),否则 Wire 会把它当成“服务提供者”尝试注入,引发类型不匹配错误。


















