业务逻辑必须写在internal/biz目录,service层仅做协议转换,data层只负责数据存取;biz层定义领域模型、接口与核心规则,通过interface依赖解耦,禁止硬编码第三方sdk或框架类型。

业务逻辑写在哪:先搞清 internal/biz 目录的职责边界
Kratos 的 internal/biz 是唯一合法的业务逻辑存放位置,不是 internal/service,也不是 internal/data。service 层只做协议转换(HTTP/gRPC 请求 → biz 接口调用),data 层只负责数据存取(CRUD、缓存读写),biz 才是真正处理“注册要校验手机号格式、发短信前要查频次、验证码要加 Redis 过期时间”这类规则的地方。
常见错误现象:internal/service 里直接写 SQL、调第三方短信接口、拼接返回结构体——这会导致测试难、复用差、违反分层契约。
- 所有领域模型(如
User、VerificationCode)定义在internal/biz/model.go - 核心业务方法(如
CreateUser、SendCode)必须声明在internal/biz/<code>xxx.go 的 interface 中 - 具体实现写在
internal/biz/<code>xxx_impl.go,由 Wire 或 Kratos-Bootstrap 自动注入
如何定义和使用 biz 接口:避免硬编码依赖
Kratos 强制要求 biz 层通过 interface 描述能力,而非直接依赖 concrete type。比如发短信不能写 smsClient.Send(...),而要先定义:
type SMSService interface {
Send(phone string, content string) error
}
然后在 internal/biz/<code>verification.go 中依赖它:
type VerificationUsecase struct {
sms SMSService
repo VerificationRepo
}
func NewVerificationUsecase(sms SMSService, repo VerificationRepo) *VerificationUsecase {
return &VerificationUsecase{sms: sms, repo: repo}
}
这样做的好处是:单元测试时可轻松 mock SMSService,不触发真实短信;未来换腾讯云短信 SDK 时,只需替换实现,biz 逻辑零修改。
容易踩的坑:internal/biz 直接 import github.com/tencentcloud/tencentcloud-sdk-go —— 这会污染 biz 层,破坏可测试性与可移植性。
biz 方法怎么写:聚焦单一职责 + 显式错误分类
一个典型的 biz 方法应该只做一件事,并明确返回领域相关错误,而不是泛化的 error。Kratos v3 鼓励用自定义错误类型区分失败原因:
var (
ErrPhoneInvalid = errors.New("phone number format invalid")
ErrCodeExhausted = errors.New("sms send frequency exceeded")
)
func (uc *VerificationUsecase) SendCode(ctx context.Context, phone string) error {
if !isValidPhone(phone) {
return ErrPhoneInvalid
}
if uc.isExhausted(ctx, phone) {
return ErrCodeExhausted
}
return uc.sms.Send(phone, generateCode())
}
这种写法让 service 层能精准判断错误类型并返回对应 HTTP 状态码(比如 ErrPhoneInvalid → 400,ErrCodeExhausted → 429),而不是统一返回 500。
性能影响:避免在 biz 方法里做耗时操作(如同步调用外部 HTTP、未加 context 超时的 DB 查询)。所有 I/O 必须带 ctx 并设置合理 deadline。
什么时候该把逻辑拆进 biz:警惕“伪业务代码”
很多开发者把参数校验、JWT 解析、日志打点、中间件鉴权逻辑误当成业务逻辑塞进 biz,这是典型误区。这些属于 transport 或 middleware 职责:
- JWT token 解析和用户 ID 提取 → 放在
middleware/auth.go,注入到context - 请求参数格式校验(如手机号长度、邮箱正则)→ 放在
middleware/validation.go或 proto 的validate规则中 - 记录“用户 A 在 X 时间调用了发码接口” → 放在
middleware/logging.go,而非 biz 里写log.Info(...)
真正属于 biz 的,是那些“离开这个服务就无法独立成立”的逻辑:比如“同一个手机号 60 秒内最多发 3 次”,它依赖本服务的 Redis 计数器和业务规则,不能被其他服务复用或替代。
最容易被忽略的一点:biz 方法内部不应出现任何框架相关类型(如 *http.Request、grpc.ServerStream)。它必须是纯 Go,只依赖标准库和 context。











