Biz层不能直接调用data实现,因其是业务逻辑组装层而非数据操作层,须通过接口隔离依赖;应定义UserRepo等接口,由data层实现并经Wire注入,避免import cycle与违反依赖倒置原则。

为什么Biz层不能直接调用data实现?
Biz层是业务逻辑的组装层,不是数据操作层。它只声明 UserRepo 接口,不关心底层是 MySQL、Redis 还是内存 Map。一旦你在 biz/user.go 里 import data 包或直接 new Ent 客户端,就违反了依赖倒置原则——后续换存储时,biz 层代码必须跟着改,单元测试也 mock 不了。
- 正确做法:biz 层只定义接口(如
GetUser(ctx, id)),由 data 层实现,再通过 Wire 注入 - 错误信号:go test 报错
import cycle not allowed,或go list显示 biz → data 反向依赖 - Wire 生成失败常见原因:biz 包里出现了
"ent"、"sql"、"redis"等具体驱动 import
如何定义和使用 Biz 模型与方法?
Biz 层模型不是数据库实体的镜像,而是面向业务语义的结构。比如 Ent 生成的 User 有 PasswordSalt 字段,但 biz 层的 User 应该只暴露 ID、Name、CreatedAt 等对外可见字段,密码相关逻辑应封装在方法里(如 CheckPassword(raw string) bool)。
- 模型文件放在
/internal/biz/user.go,结构体名首字母大写(如User),字段按业务需要精简 - 业务方法写在模型上,不依赖外部包:例如
func (u *User) IsAdmin() bool { return u.Role == "admin" } - 不要在 biz 层做 HTTP 状态码判断、JSON 序列化、日志埋点——这些属于 service 或 middleware 职责
Repo 接口怎么设计才利于测试和替换?
UserRepo 接口必须窄、稳定、无副作用。它只描述“能做什么”,不暴露“怎么做”。例如不要定义 GetUserWithOrder(ctx, id) (*User, []*Order, error),而应拆成 GetUser + ListOrdersByUserID,否则后续加缓存、分库、读写分离时接口要反复改。
- 接口方法名用动词开头:
GetUser、CreateUser、UpdateUserStatus,避免Find、Select等 SQL 色彩过重的词 - 参数尽量用 biz 模型或 ID 类型(如
int64、string),别传*sql.Tx或context.Context以外的框架类型 - 返回值统一为
(*T, error)形式,错误类型用 biz 层自定义的 error code(如user.ErrNotFound),不用ent.IsNotFound
常见踩坑:Biz 层误用 context 或 log
很多开发者在 biz 方法里直接调用 log.Printf 或往 ctx 塞 traceID,这会让 biz 层耦合传输细节。Kratos 的设计是:service 层负责接收请求、注入上下文、记录日志;biz 层只专注“给定输入,输出结果”。
- 禁止在 biz 方法中调用
log、zap、kitlog等日志库 - 禁止用
context.WithValue往 ctx 写业务字段(如 user_id),应由 service 层解析并传参 - 如果真需要上下文信息(如租户 ID),应作为显式参数传入,例如
GetUser(ctx, tenantID, userID)
最易被忽略的一点:biz 层的每个方法都应该是可独立单元测试的,且测试时不依赖任何外部服务。如果你发现写个 test 要起 MySQL、配 Redis、mock HTTP client,那大概率是 biz 和 data 的边界已经模糊了。











